From 4ecdba1ebb3ec42c6da7156ba94f32143cd37ffc Mon Sep 17 00:00:00 2001 From: Hardik dadhich Date: Wed, 4 Mar 2020 00:13:20 +0530 Subject: [PATCH 001/101] Find Total Number of Connected Component in Graph --- .../count_connected_number_of_component.py | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 algorithms/graph/count_connected_number_of_component.py diff --git a/algorithms/graph/count_connected_number_of_component.py b/algorithms/graph/count_connected_number_of_component.py new file mode 100644 index 000000000..19caec45a --- /dev/null +++ b/algorithms/graph/count_connected_number_of_component.py @@ -0,0 +1,54 @@ +#count connected no of component using DFS +''' +In graph theory, a component, sometimes called a connected component, +of an undirected graph is a subgraph in which any +two vertices are connected to each other by paths. + +Example: + + + 1 3------------7 + | + | + 2--------4 + | | + | | output = 2 + 6--------5 + +''' + +# Code is Here + +def dfs(source,visited): + ''' Function that performs DFS ''' + + visited[source] = True + for child in l[source]: + if not visited[child]: + dfs(child,visited) + +def count_components(l,size): + ''' + Function that counts the Connected components on bases of DFS. + return type : int + ''' + + count = 0 + visited = [False]*(size+1) + for i in range(1,size+1): + if not visited[i]: + dfs(i,visited) + count+=1 + return count + + +# Driver code +if __name__ == '__main__': + n,m = map(int, input("Enter the Number of Nodes and Edges \n").split(' ')) + l = [[] for _ in range(n+1)] + for i in range(m): + print("Enter the edge's Nodes in form of a b\n") + a,b = map(int,input().split(' ')) + l[a].append(b) + l[b].append(a) + print("Total number of Connected Components are : ", count_components(l,n)) From c117e195f0fe51b0d36754f842b32ca352b14af1 Mon Sep 17 00:00:00 2001 From: Hardik dadhich Date: Sat, 7 Mar 2020 14:02:19 +0530 Subject: [PATCH 002/101] Added unit tests --- README.md | 1 + .../count_connected_number_of_component.py | 8 ++-- tests/test_count_connected_components.py | 37 +++++++++++++++++++ 3 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 tests/test_count_connected_components.py diff --git a/README.md b/README.md index 90a062f29..06fb2d4c4 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,7 @@ If you want to uninstall algorithms, it is as simple as: - [maximum_flow_dfs](algorithms/graph/maximum_flow_dfs.py) - [all_pairs_shortest_path](algorithms/graph/all_pairs_shortest_path.py) - [bellman_ford](algorithms/graph/bellman_ford.py) + - [Count Connected Components](algoritms/graph/count_connected_number_of_component.py) - [heap](algorithms/heap) - [merge_sorted_k_lists](algorithms/heap/merge_sorted_k_lists.py) - [skyline](algorithms/heap/skyline.py) diff --git a/algorithms/graph/count_connected_number_of_component.py b/algorithms/graph/count_connected_number_of_component.py index 19caec45a..a944cf096 100644 --- a/algorithms/graph/count_connected_number_of_component.py +++ b/algorithms/graph/count_connected_number_of_component.py @@ -19,13 +19,13 @@ # Code is Here -def dfs(source,visited): +def dfs(source,visited,l): ''' Function that performs DFS ''' visited[source] = True for child in l[source]: if not visited[child]: - dfs(child,visited) + dfs(child,visited,l) def count_components(l,size): ''' @@ -37,11 +37,11 @@ def count_components(l,size): visited = [False]*(size+1) for i in range(1,size+1): if not visited[i]: - dfs(i,visited) + dfs(i,visited,l) count+=1 return count - + # Driver code if __name__ == '__main__': n,m = map(int, input("Enter the Number of Nodes and Edges \n").split(' ')) diff --git a/tests/test_count_connected_components.py b/tests/test_count_connected_components.py new file mode 100644 index 000000000..4348ab088 --- /dev/null +++ b/tests/test_count_connected_components.py @@ -0,0 +1,37 @@ +import unittest +from algorithms.graph import count_connected_number_of_component + +class TestConnectedComponentInGraph(unittest.TestCase): + """ Class """ + + def test_count_connected_components(self): + """ + Test Function that test the different cases of count connected components + + 0----------2 1--------5 3 + | + | + 4 + + output = 3 + + """ + expected_result = 3 + l = [[2], + [5], + [0,4], + [], + [2], + [1] + ] + + size = 5 + result = count_connected_number_of_component.count_components(l,size) + self.assertEqual(result,expected_result) + +if __name__ == '__main__': + + unittest.main() + + + From 25da9ad92062537d5f98e8e2aa6db1f16e7d8c56 Mon Sep 17 00:00:00 2001 From: Hardik dadhich Date: Sat, 7 Mar 2020 14:05:57 +0530 Subject: [PATCH 003/101] fixing spaces --- tests/test_count_connected_components.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tests/test_count_connected_components.py b/tests/test_count_connected_components.py index 4348ab088..b9b1620c5 100644 --- a/tests/test_count_connected_components.py +++ b/tests/test_count_connected_components.py @@ -1,22 +1,25 @@ +#import modules import unittest from algorithms.graph import count_connected_number_of_component class TestConnectedComponentInGraph(unittest.TestCase): - """ Class """ + """ Class to performs unit tests """ def test_count_connected_components(self): """ Test Function that test the different cases of count connected components - 0----------2 1--------5 3 - | - | - 4 - - output = 3 + 0----------2 1--------5 3 + | + | + 4 + + output = 3 """ expected_result = 3 + + # adjacency list representation of graph l = [[2], [5], [0,4], From e5c4699cccbc9b5e02afa8c03b7b098bcef548c8 Mon Sep 17 00:00:00 2001 From: Hardik dadhich Date: Sat, 7 Mar 2020 14:08:12 +0530 Subject: [PATCH 004/101] fixing spaces --- tests/test_count_connected_components.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_count_connected_components.py b/tests/test_count_connected_components.py index b9b1620c5..a7e1fec6d 100644 --- a/tests/test_count_connected_components.py +++ b/tests/test_count_connected_components.py @@ -9,7 +9,7 @@ def test_count_connected_components(self): """ Test Function that test the different cases of count connected components - 0----------2 1--------5 3 + 0----------2 1--------5 3 | | 4 From 9034015aafb6d05202b434f897c9340f1817ecfa Mon Sep 17 00:00:00 2001 From: Hardik dadhich Date: Fri, 13 Mar 2020 01:36:36 +0530 Subject: [PATCH 005/101] adding more unit-tests --- tests/test_count_connected_components.py | 40 ----------------- tests/test_graph.py | 56 +++++++++++++++++++++++- 2 files changed, 54 insertions(+), 42 deletions(-) delete mode 100644 tests/test_count_connected_components.py diff --git a/tests/test_count_connected_components.py b/tests/test_count_connected_components.py deleted file mode 100644 index a7e1fec6d..000000000 --- a/tests/test_count_connected_components.py +++ /dev/null @@ -1,40 +0,0 @@ -#import modules -import unittest -from algorithms.graph import count_connected_number_of_component - -class TestConnectedComponentInGraph(unittest.TestCase): - """ Class to performs unit tests """ - - def test_count_connected_components(self): - """ - Test Function that test the different cases of count connected components - - 0----------2 1--------5 3 - | - | - 4 - - output = 3 - - """ - expected_result = 3 - - # adjacency list representation of graph - l = [[2], - [5], - [0,4], - [], - [2], - [1] - ] - - size = 5 - result = count_connected_number_of_component.count_components(l,size) - self.assertEqual(result,expected_result) - -if __name__ == '__main__': - - unittest.main() - - - diff --git a/tests/test_graph.py b/tests/test_graph.py index c691f33d2..b12646a0e 100644 --- a/tests/test_graph.py +++ b/tests/test_graph.py @@ -9,7 +9,7 @@ from algorithms.graph import all_pairs_shortest_path from algorithms.graph import bellman_ford from algorithms.graph import bellman_ford - +from algorithms.graph import count_connected_number_of_component import unittest @@ -208,4 +208,56 @@ def test_bellman_ford(self): 'e': {'a': 7, 'b': 5, 'd': 1} } - self.assertEqual(True, bellman_ford(graph2, 'a')) +class TestConnectedComponentInGraph(unittest.TestCase): + """ + Class for testing different cases for connected components in graph + """ + def test_count_connected_components(self): + """ + Test Function that test the different cases of count connected components + + 0----------2 1--------5 3 + | + | + 4 + + output = 3 + """ + expected_result = 3 + + # adjacency list representation of graph + l = [[2], + [5], + [0,4], + [], + [2], + [1] + ] + + size = 5 + result = count_connected_number_of_component.count_components(l,size) + self.assertEqual(result,expected_result) + + def test_connected_components_with_empty_graph(self): + + """ + input : + output : 0 + """ + + l = [[]] + expected_result = 0 + size = 0 + result = count_connected_number_of_component.count_components(l,size) + self.assertEqual(result,expected_result) + + def test_connected_components_without_edges_graph(self): + """ + input : 0 2 3 4 + output : 4 + """ + l = [[0],[],[2],[3],[4]] + size = 4 + expected_result = 4 + result = count_connected_number_of_component.count_components(l,size) + self.assertEqual(result,expected_result) From ae3824422eac2ba796ad117fd7f8f77a5bb9e08b Mon Sep 17 00:00:00 2001 From: Hardik dadhich Date: Fri, 13 Mar 2020 10:10:50 +0530 Subject: [PATCH 006/101] adding line which is deleted --- tests/test_graph.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_graph.py b/tests/test_graph.py index b12646a0e..b9794edf0 100644 --- a/tests/test_graph.py +++ b/tests/test_graph.py @@ -208,6 +208,9 @@ def test_bellman_ford(self): 'e': {'a': 7, 'b': 5, 'd': 1} } + self.assertEqual(True, bellman_ford(graph2, 'a')) + + class TestConnectedComponentInGraph(unittest.TestCase): """ Class for testing different cases for connected components in graph From 7cdd4170947e39a8d206b4b329f18140a66413bb Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Fri, 13 Mar 2020 23:41:17 +0800 Subject: [PATCH 007/101] Remove temporary variable --- algorithms/dp/house_robber.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/algorithms/dp/house_robber.py b/algorithms/dp/house_robber.py index a2a8a6d92..5a359f93d 100644 --- a/algorithms/dp/house_robber.py +++ b/algorithms/dp/house_robber.py @@ -15,7 +15,5 @@ def house_robber(houses): last, now = 0, 0 for house in houses: - tmp = now - now = max(last + house, now) - last = tmp + last, now = now, max(last + house, now) return now From 599144db8849d2bb30e64b4c26f12583225340bf Mon Sep 17 00:00:00 2001 From: Erick Lara Date: Tue, 17 Mar 2020 00:07:40 -0600 Subject: [PATCH 008/101] Small fix on the test description to match the code --- tests/test_graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_graph.py b/tests/test_graph.py index b9794edf0..83d35293b 100644 --- a/tests/test_graph.py +++ b/tests/test_graph.py @@ -219,7 +219,7 @@ def test_count_connected_components(self): """ Test Function that test the different cases of count connected components - 0----------2 1--------5 3 + 2----------0 1--------5 3 | | 4 From 4a7234d4592166a1a13bc6b8e8b3b201019df23b Mon Sep 17 00:00:00 2001 From: Himanshu Airan <62210670+Himanshu-77@users.noreply.github.com> Date: Wed, 13 May 2020 18:30:57 +0530 Subject: [PATCH 009/101] Create prims_minimum_spanning.py code implemented for Prim's Algorithm to find minimum spanning tree. Although there was file named minimum_spanning_tree.py in graph section but contain only Kruskal Algorithm . Prim's Algo. was still missing --- algorithms/graph/prims_minimum_spanning.py | 36 ++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 algorithms/graph/prims_minimum_spanning.py diff --git a/algorithms/graph/prims_minimum_spanning.py b/algorithms/graph/prims_minimum_spanning.py new file mode 100644 index 000000000..adb31910c --- /dev/null +++ b/algorithms/graph/prims_minimum_spanning.py @@ -0,0 +1,36 @@ +import heapq # for priority queue + +# input number of nodes and edges in graph +n, e = map (int,input().split()) + +# initializing empty graph as a dictionary (of the form {int:list}) +g = dict (zip ([i for i in range(1,n+1)],[[] for i in range(n)])) + +# input graph data +for i in range(e): + a, b, c = map (int,input().split()) + g[a].append([c,b]) + g[b].append([c,a]) + +vis = [] +s = [[0,1]] +prim = [] +mincost = 0 + +# prim's algo. to find weight of minimum spanning tree +while (len(s)>0): + v = heapq.heappop(s) + x = v[1] + if (x in vis): + continue + + mincost += v[0] + prim.append(x) + vis.append(x) + + for j in g[x]: + i = j[-1] + if(i not in vis): + heapq.heappush(s,j) + +print(mincost) From d7ceafc0497022276521f42e5686b0361f73f7fe Mon Sep 17 00:00:00 2001 From: Himanshu Airan <62210670+Himanshu-77@users.noreply.github.com> Date: Wed, 13 May 2020 22:29:53 +0530 Subject: [PATCH 010/101] Update prims_minimum_spanning.py function and sample test cases added --- algorithms/graph/prims_minimum_spanning.py | 90 ++++++++++++++-------- 1 file changed, 59 insertions(+), 31 deletions(-) diff --git a/algorithms/graph/prims_minimum_spanning.py b/algorithms/graph/prims_minimum_spanning.py index adb31910c..060235eac 100644 --- a/algorithms/graph/prims_minimum_spanning.py +++ b/algorithms/graph/prims_minimum_spanning.py @@ -1,36 +1,64 @@ import heapq # for priority queue -# input number of nodes and edges in graph -n, e = map (int,input().split()) +# prim's algo. to find weight of minimum spanning tree +def prims(graph): + vis=[] + s=[[0,1]] + prim = [] + mincost=0 + + while(len(s)>0): + v=heapq.heappop(s) + x=v[1] + if(x in vis): + continue -# initializing empty graph as a dictionary (of the form {int:list}) -g = dict (zip ([i for i in range(1,n+1)],[[] for i in range(n)])) + mincost += v[0] + prim.append(x) + vis.append(x) -# input graph data -for i in range(e): - a, b, c = map (int,input().split()) - g[a].append([c,b]) - g[b].append([c,a]) - -vis = [] -s = [[0,1]] -prim = [] -mincost = 0 + for j in g[x]: + i=j[-1] + if(i not in vis): + heapq.heappush(s,j) -# prim's algo. to find weight of minimum spanning tree -while (len(s)>0): - v = heapq.heappop(s) - x = v[1] - if (x in vis): - continue - - mincost += v[0] - prim.append(x) - vis.append(x) - - for j in g[x]: - i = j[-1] - if(i not in vis): - heapq.heappush(s,j) - -print(mincost) + return mincost + + + +if __name__=="__main__": + + # input number of nodes and edges in graph + n,e = map(int,input().split()) + + # initializing empty graph as a dictionary (of the form {int:list}) + g=dict(zip([i for i in range(1,n+1)],[[] for i in range(n)])) + + # input graph data + for i in range(e): + a,b,c=map(int,input().split()) + g[a].append([c,b]) + g[b].append([c,a]) + + # print weight of minimum spanning tree + print(prims(g)) + + ''' tests- + Input : 4 5 + 1 2 7 + 1 4 6 + 2 4 9 + 4 3 8 + 2 3 6 + Output : 19 + + + Input : 5 6 + 1 2 3 + 1 3 8 + 2 4 5 + 3 4 2 + 3 5 4 + 4 5 6 + Output : 14 + ''' From 52d835ec8a3dfec53c3cab23598be6f63da9addc Mon Sep 17 00:00:00 2001 From: Himanshu Airan <62210670+Himanshu-77@users.noreply.github.com> Date: Thu, 14 May 2020 15:24:30 +0530 Subject: [PATCH 011/101] Update prims_minimum_spanning.py function created --- algorithms/graph/prims_minimum_spanning.py | 60 +++++++--------------- 1 file changed, 19 insertions(+), 41 deletions(-) diff --git a/algorithms/graph/prims_minimum_spanning.py b/algorithms/graph/prims_minimum_spanning.py index 060235eac..7e290d81a 100644 --- a/algorithms/graph/prims_minimum_spanning.py +++ b/algorithms/graph/prims_minimum_spanning.py @@ -1,7 +1,24 @@ +''' +This Prim's Algorithm Code is for finding weight of minimum spanning tree +of a connected graph. +For argument graph, it should be a dictionary type +such as +graph = { + 'a': [ [3, 'b'], [8,'c'] ], + 'b': [ [3, 'a'], [5, 'd'] ], + 'c': [ [8, 'a'], [2, 'd'], [4, 'e'] ], + 'd': [ [5, 'b'], [2, 'c'], [6, 'e'] ], + 'e': [ [4, 'c'], [6, 'd'] ] +} + +where 'a','b','c','d','e' are nodes (these can be 1,2,3,4,5 as well) +''' + + import heapq # for priority queue # prim's algo. to find weight of minimum spanning tree -def prims(graph): +def prims(graph_used): vis=[] s=[[0,1]] prim = [] @@ -17,48 +34,9 @@ def prims(graph): prim.append(x) vis.append(x) - for j in g[x]: + for j in graph_used[x]: i=j[-1] if(i not in vis): heapq.heappush(s,j) return mincost - - - -if __name__=="__main__": - - # input number of nodes and edges in graph - n,e = map(int,input().split()) - - # initializing empty graph as a dictionary (of the form {int:list}) - g=dict(zip([i for i in range(1,n+1)],[[] for i in range(n)])) - - # input graph data - for i in range(e): - a,b,c=map(int,input().split()) - g[a].append([c,b]) - g[b].append([c,a]) - - # print weight of minimum spanning tree - print(prims(g)) - - ''' tests- - Input : 4 5 - 1 2 7 - 1 4 6 - 2 4 9 - 4 3 8 - 2 3 6 - Output : 19 - - - Input : 5 6 - 1 2 3 - 1 3 8 - 2 4 5 - 3 4 2 - 3 5 4 - 4 5 6 - Output : 14 - ''' From 581be000530d67b38038d140c457308ee0afd319 Mon Sep 17 00:00:00 2001 From: Himanshu Airan <62210670+Himanshu-77@users.noreply.github.com> Date: Thu, 14 May 2020 15:42:16 +0530 Subject: [PATCH 012/101] Update test_graph.py unittest added for prims_minimum_spanning.py --- tests/test_graph.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/test_graph.py b/tests/test_graph.py index 83d35293b..325b5d896 100644 --- a/tests/test_graph.py +++ b/tests/test_graph.py @@ -10,6 +10,7 @@ from algorithms.graph import bellman_ford from algorithms.graph import bellman_ford from algorithms.graph import count_connected_number_of_component +from algorithms.graph import prims_minimum_spanning import unittest @@ -264,3 +265,23 @@ def test_connected_components_without_edges_graph(self): expected_result = 4 result = count_connected_number_of_component.count_components(l,size) self.assertEqual(result,expected_result) + + +class PrimsMinimumSpanning(unittest.TestCase): + def test_prim_spanning(self): + graph1 = { + 1 : [ [3, 2], [8, 3] ], + 2 : [ [3, 1], [5, 4] ], + 3 : [ [8, 1], [2, 4], [4, 5] ], + 4 : [ [5, 2], [2, 3], [6, 5] ], + 5 : [ [4, 3], [6, 4] ] + } + self.assertEqual(14, prims_minimum_spanning(graph1)) + + graph2 = { + 1 : [ [7, 2], [6, 4] ], + 2 : [ [7, 1], [9, 4], [6, 3] ], + 3 : [ [8, 4], [6, 2] ], + 4 : [ [6, 1], [9, 2], [8, 3] ] + } + self.assertEqual(19, prims_minimum_spanning(graph2)) From 2f8be626a187724076ab7b603dd33d6f3f7273b9 Mon Sep 17 00:00:00 2001 From: Himanshu Airan <62210670+Himanshu-77@users.noreply.github.com> Date: Thu, 14 May 2020 15:43:45 +0530 Subject: [PATCH 013/101] Update prims_minimum_spanning.py function name changed --- algorithms/graph/prims_minimum_spanning.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/algorithms/graph/prims_minimum_spanning.py b/algorithms/graph/prims_minimum_spanning.py index 7e290d81a..02295c4c2 100644 --- a/algorithms/graph/prims_minimum_spanning.py +++ b/algorithms/graph/prims_minimum_spanning.py @@ -18,7 +18,7 @@ import heapq # for priority queue # prim's algo. to find weight of minimum spanning tree -def prims(graph_used): +def prims_minimum_spanning(graph_used): vis=[] s=[[0,1]] prim = [] From 3e7bd31d8b5106addf7e638aca8b0147a6a1c7b2 Mon Sep 17 00:00:00 2001 From: Himanshu Airan <62210670+Himanshu-77@users.noreply.github.com> Date: Fri, 15 May 2020 00:39:12 +0530 Subject: [PATCH 014/101] Update README.md link added for new file prims_minimum_spanning.py --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 06fb2d4c4..1fed65ab4 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,7 @@ If you want to uninstall algorithms, it is as simple as: - [markov_chain](algorithms/graph/markov_chain.py) - [minimum_spanning_tree](algorithms/graph/minimum_spanning_tree.py) - [satisfiability](algorithms/graph/satisfiability.py) + - [minimum_spanning_tree_prims](algorithms/graph/prims_minimum_spanning.py) - [tarjan](algorithms/graph/tarjan.py) - [traversal](algorithms/graph/traversal.py) - [maximum_flow](algorithms/graph/maximum_flow.py) From 3e1a96516a2b5e7368684bb4eb3a31ac7aab6e93 Mon Sep 17 00:00:00 2001 From: NLe1 Date: Sun, 17 May 2020 17:57:07 -0700 Subject: [PATCH 015/101] fix import prism_minimum_spanning import --- algorithms/graph/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/algorithms/graph/__init__.py b/algorithms/graph/__init__.py index 6675e6362..3bf3a1c8c 100644 --- a/algorithms/graph/__init__.py +++ b/algorithms/graph/__init__.py @@ -5,3 +5,4 @@ from .maximum_flow_dfs import * from .all_pairs_shortest_path import * from .bellman_ford import * +from .prims_minimum_spanning import * From db43efdb2e809043f81d6758ce541ead91d06314 Mon Sep 17 00:00:00 2001 From: Swapnanil Dutta <47251193+swapnanildutta@users.noreply.github.com> Date: Wed, 3 Jun 2020 04:30:23 +0530 Subject: [PATCH 016/101] Added Fibonacci Description (#658) Added the basic description of Fibonacci Numbers and the main aim of the functions. --- algorithms/dp/fib.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/algorithms/dp/fib.py b/algorithms/dp/fib.py index 6762e90db..7a6fd7140 100644 --- a/algorithms/dp/fib.py +++ b/algorithms/dp/fib.py @@ -1,3 +1,17 @@ +''' +In mathematics, the Fibonacci numbers, commonly denoted Fn, form a sequence, called the Fibonacci sequence, +such that each number is the sum of the two preceding ones, starting from 0 and 1. +That is, + F0=0 , F1=1 +and + Fn= F(n-1) + F(n-2) +The Fibonacci numbers are the numbers in the following integer sequence. + 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ……. + +In mathematical terms, the sequence Fn of Fibonacci numbers is defined by the recurrence relation + +Here, given a number n, print n-th Fibonacci Number. +''' def fib_recursive(n): """[summary] Computes the n-th fibonacci number recursive. From e6f1dbe72e6ba1bf4b4e72053b12254f8988f97b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E4=BF=8A=E4=B9=89?= <32086937+Sean16SYSU@users.noreply.github.com> Date: Sun, 21 Jun 2020 12:55:03 +0800 Subject: [PATCH 017/101] Manacher's algorithms - in Python (#575) * add_integer_divide * add_integer_divide * add_integer_divide * add_integer_divide * README updated * Manacher's algorithm * Manacher's algorithm * add Manacher's algorithm * with more snack test case * more snack cases for testing files * change the name of function `longestPalindrome` to `longest_palindrome` * test changed because the name of function changed --- README.md | 7 +-- algorithms/strings/__init__.py | 2 +- .../strings/longest_palindromic_substring.py | 44 +++++++++++++++++++ tests/test_strings.py | 23 +++++++++- 4 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 algorithms/strings/longest_palindromic_substring.py diff --git a/README.md b/README.md index 1fed65ab4..79c1d0808 100644 --- a/README.md +++ b/README.md @@ -329,9 +329,10 @@ If you want to uninstall algorithms, it is as simple as: - [longest_common_prefix](algorithms/strings/longest_common_prefix.py) - [rotate](algorithms/strings/rotate.py) - [first_unique_char](algorithms/strings/first_unique_char.py) - - [repeat_substring](algorithms/strings/repeat_substring.py) - - [atbash_cipher](algorithms/strings/atbash_cipher.py) - - [knuth_morris_pratt](algorithms/strings/knuth_morris_pratt.py) + - [repeat_substring](algorithms/strings/repeat_substring.py) + - [atbash_cipher](algorithms/strings/atbash_cipher.py) + - [longest_palindromic_substring](algorithms/strings/longest_palindromic_substring.py) + - [knuth_morris_pratt](algorithms/strings/knuth_morris_pratt.py) - [tree](algorithms/tree) - [bst](algorithms/tree/bst) - [array_to_bst](algorithms/tree/bst/array_to_bst.py) diff --git a/algorithms/strings/__init__.py b/algorithms/strings/__init__.py index 3e6bc19d4..72ea7d70d 100644 --- a/algorithms/strings/__init__.py +++ b/algorithms/strings/__init__.py @@ -35,5 +35,5 @@ from .first_unique_char import * from .repeat_substring import * from .atbash_cipher import * +from .longest_palindromic_substring import * from .knuth_morris_pratt import * - diff --git a/algorithms/strings/longest_palindromic_substring.py b/algorithms/strings/longest_palindromic_substring.py new file mode 100644 index 000000000..ad0c831b9 --- /dev/null +++ b/algorithms/strings/longest_palindromic_substring.py @@ -0,0 +1,44 @@ +''' +Given string s, find the longest palindromic substring. + +Example1: + +* input: "dasdasdasdasdasdadsa" +* output: "asdadsa" + +Example2: + +* input: "acdbbdaa" +* output: "dbbd" + +Manacher's algorithm + +''' + +def longest_palindrome(s): + if len(s) < 2: + return s + + n_str = '#' + '#'.join(s) + '#' + p = [0] * len(n_str) + mx, loc = 0, 0 + index, maxlen = 0, 0 + for i in range(len(n_str)): + if i < mx and 2 * loc - i < len(n_str): + p[i] = min(mx - i, p[2 * loc - i]) + else: + p[i] = 1 + + while p[i] + i < len(n_str) and i - p[i] >= 0 and n_str[ + i - p[i]] == n_str[i + p[i]]: + p[i] += 1 + + if i + p[i] > mx: + mx = i + p[i] + loc = i + + if p[i] > maxlen: + index = i + maxlen = p[i] + s = n_str[index - p[index] + 1:index + p[index]] + return s.replace('#', '') diff --git a/tests/test_strings.py b/tests/test_strings.py index 48fafd2a5..b8750b1f8 100644 --- a/tests/test_strings.py +++ b/tests/test_strings.py @@ -38,6 +38,7 @@ first_unique_char, repeat_substring, atbash, + longest_palindrome, knuth_morris_pratt ) @@ -527,24 +528,44 @@ class TestAtbashCipher(unittest.TestCase): Arguments: unittest {[type]} -- [description] """ - + def test_atbash_cipher(self): self.assertEqual("zyxwvutsrqponml", atbash("abcdefghijklmno")) self.assertEqual("KbgslM", atbash("PythoN")) self.assertEqual("AttaCK at DawN", atbash("ZggzXP zg WzdM")) self.assertEqual("ZggzXP zg WzdM", atbash("AttaCK at DawN")) + + + +class TestLongestPalindromicSubstring(unittest.TestCase): + """[summary] + Test for the file longest_palindromic_substring.py + + Arguments: + unittest {[type]} -- [description] + """ + + def test_longest_palindromic_substring(self): + self.assertEqual("bb", longest_palindrome("cbbd")) + self.assertEqual("abba", longest_palindrome("abba")) + self.assertEqual("asdadsa", longest_palindrome("dasdasdasdasdasdadsa")) + self.assertEqual("abba", longest_palindrome("cabba")) + class TestKnuthMorrisPratt(unittest.TestCase): """[summary] Test for the file knuth_morris_pratt.py + Arguments: unittest {[type]} -- [description] """ + def test_knuth_morris_pratt(self): self.assertEqual([0, 1, 2, 3, 4], knuth_morris_pratt("aaaaaaa", "aaa")) self.assertEqual([0, 4], knuth_morris_pratt("abcdabc", "abc")) self.assertEqual([], knuth_morris_pratt("aabcdaab", "aba")) + if __name__ == "__main__": unittest.main() From ab5f01db209895abd424907514077fb087cc8d4a Mon Sep 17 00:00:00 2001 From: blucruz Date: Sun, 21 Jun 2020 22:22:56 +0800 Subject: [PATCH 018/101] Update flatten.py (#647) --- algorithms/arrays/flatten.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/algorithms/arrays/flatten.py b/algorithms/arrays/flatten.py index f19156a09..52f4ec56d 100644 --- a/algorithms/arrays/flatten.py +++ b/algorithms/arrays/flatten.py @@ -11,7 +11,7 @@ def flatten(input_arr, output_arr=None): if output_arr is None: output_arr = [] for ele in input_arr: - if isinstance(ele, Iterable): + if not isinstance(ele, str) and isinstance(ele, Iterable): flatten(ele, output_arr) #tail-recursion else: output_arr.append(ele) #produce the result @@ -25,7 +25,7 @@ def flatten_iter(iterable): returns generator which produces one dimensional output. """ for element in iterable: - if isinstance(element, Iterable): + if not isinstance(element, str) and isinstance(element, Iterable): yield from flatten_iter(element) else: yield element From 20c1c4ec8de2a36a0640ad071e003f843c5c6c75 Mon Sep 17 00:00:00 2001 From: Rakshit Parashar <34675136+rishu2403@users.noreply.github.com> Date: Sun, 21 Jun 2020 19:53:32 +0530 Subject: [PATCH 019/101] Update binary_search.py (#657) --- algorithms/search/binary_search.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/algorithms/search/binary_search.py b/algorithms/search/binary_search.py index 644f98f8e..d8bccf06b 100644 --- a/algorithms/search/binary_search.py +++ b/algorithms/search/binary_search.py @@ -1,9 +1,13 @@ # # Binary search works for a sorted array. # Note: The code logic is written for an array sorted in -# increasing order. -# T(n): O(log n) -# +# increasing order. +#For Binary Search, T(N) = T(N/2) + O(1) // the recurrence relation +#Apply Masters Theorem for computing Run time complexity of recurrence relations : T(N) = aT(N/b) + f(N) +#Here, a = 1, b = 2 => log (a base b) = 1 +# also, here f(N) = n^c log^k(n) //k = 0 & c = log (a base b) So, T(N) = O(N^c log^(k+1)N) = O(log(N)) + + def binary_search(array, query): lo, hi = 0, len(array) - 1 while lo <= hi: From 82500bad28b9773e62ef8d6d629d574d5c0156ff Mon Sep 17 00:00:00 2001 From: Liam Date: Mon, 29 Jun 2020 17:02:14 -0400 Subject: [PATCH 020/101] add pigeonhole sort --- README.md | 1 + algorithms/sort/pigeonhole_sort.py | 28 ++++++++++++++++++++++++++++ tests/test_sort.py | 5 +++++ 3 files changed, 34 insertions(+) create mode 100644 algorithms/sort/pigeonhole_sort.py diff --git a/README.md b/README.md index 79c1d0808..82bb81a69 100644 --- a/README.md +++ b/README.md @@ -277,6 +277,7 @@ If you want to uninstall algorithms, it is as simple as: - [meeting_rooms](algorithms/sort/meeting_rooms.py) - [merge_sort](algorithms/sort/merge_sort.py) - [pancake_sort](algorithms/sort/pancake_sort.py) + - [pigeonhole_sort](algorithms/sort/pigeonhole_sort.py) - [quick_sort](algorithms/sort/quick_sort.py) - [radix_sort](algorithms/sort/radix_sort.py) - [selection_sort](algorithms/sort/selection_sort.py) diff --git a/algorithms/sort/pigeonhole_sort.py b/algorithms/sort/pigeonhole_sort.py new file mode 100644 index 000000000..0569aa994 --- /dev/null +++ b/algorithms/sort/pigeonhole_sort.py @@ -0,0 +1,28 @@ +""" + +https://en.wikipedia.org/wiki/Pigeonhole_sort + +Time complexity: O(n + Range) where n = number of elements and Range = possible values in the array + +Suitable for lists where the number of elements and key values are mostly the same. + +""" + + +def pigeonhole_sort(arr): + Max = max(arr) + Min = min(arr) + size = Max - Min + 1 + + holes = [0]*size + + for i in arr: + holes[i-Min] += 1 + + i = 0 + for count in range(size): + while holes[count] > 0: + holes[count] -= 1 + arr[i] = count + Min + i += 1 + return arr diff --git a/tests/test_sort.py b/tests/test_sort.py index 650f33920..e2da29132 100644 --- a/tests/test_sort.py +++ b/tests/test_sort.py @@ -9,6 +9,7 @@ insertion_sort, merge_sort, pancake_sort, + pigeonhole_sort, quick_sort, selection_sort, bucket_sort, @@ -70,6 +71,10 @@ def test_merge_sort(self): def test_pancake_sort(self): self.assertEqual([1, 5, 23, 57, 65, 1232], pancake_sort([1, 5, 65, 23, 57, 1232])) + + def test_pigeonhole_sort(self): + self.assertEqual([1, 5, 23, 57, 65, 1232], + pigeonhole_sort([1, 5, 65, 23, 57, 1232])) def test_quick_sort(self): self.assertEqual([1, 5, 23, 57, 65, 1232], From 90fafee4bae4ec3156852d741a8e9f7f22040d15 Mon Sep 17 00:00:00 2001 From: Liam Date: Mon, 29 Jun 2020 17:48:41 -0400 Subject: [PATCH 021/101] export function pigeonhole_sort --- algorithms/sort/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/algorithms/sort/__init__.py b/algorithms/sort/__init__.py index fceb3bd22..ee78300c6 100644 --- a/algorithms/sort/__init__.py +++ b/algorithms/sort/__init__.py @@ -8,6 +8,7 @@ from .insertion_sort import * from .merge_sort import * from .pancake_sort import * +from .pigeonhole_sort import * from .quick_sort import * from .selection_sort import * from .top_sort import * From e5ffcb4ecd85c2f08dc53e107713390d2b61e1ee Mon Sep 17 00:00:00 2001 From: Liam Date: Wed, 1 Jul 2020 02:56:15 -0400 Subject: [PATCH 022/101] add check pangram --- README.md | 1 + algorithms/strings/__init__.py | 1 + algorithms/strings/check_pangram.py | 10 ++++++++++ tests/test_strings.py | 6 ++++++ 4 files changed, 18 insertions(+) create mode 100644 algorithms/strings/check_pangram.py diff --git a/README.md b/README.md index 79c1d0808..f2adbaadf 100644 --- a/README.md +++ b/README.md @@ -322,6 +322,7 @@ If you want to uninstall algorithms, it is as simple as: - [judge_circle](algorithms/strings/judge_circle.py) - [strong_password](algorithms/strings/strong_password.py) - [caesar_cipher](algorithms/strings/caesar_cipher.py) + - [check_pangram](algorithms/strings/check_pangram.py - [contain_string](algorithms/strings/contain_string.py) - [count_binary_substring](algorithms/strings/count_binary_substring.py) - [repeat_string](algorithms/strings/repeat_string.py) diff --git a/algorithms/strings/__init__.py b/algorithms/strings/__init__.py index 72ea7d70d..37c83dfdd 100644 --- a/algorithms/strings/__init__.py +++ b/algorithms/strings/__init__.py @@ -25,6 +25,7 @@ from .judge_circle import * from .strong_password import * from .caesar_cipher import * +from .check_pangram import * from .contain_string import * from .count_binary_substring import * from .repeat_string import * diff --git a/algorithms/strings/check_pangram.py b/algorithms/strings/check_pangram.py new file mode 100644 index 000000000..412425793 --- /dev/null +++ b/algorithms/strings/check_pangram.py @@ -0,0 +1,10 @@ +""" +Algorithm that checks if a given string is a pangram or not +""" + +def check_pangram(input_string): + alphabet = "abcdefghijklmnopqrstuvwxyz" + for ch in alphabet: + if ch not in input_string.lower(): + return False + return True \ No newline at end of file diff --git a/tests/test_strings.py b/tests/test_strings.py index b8750b1f8..5544086fa 100644 --- a/tests/test_strings.py +++ b/tests/test_strings.py @@ -28,6 +28,7 @@ judge_circle, strong_password, caesar_cipher, + check_pangram, contain_string, count_binary_substring, repeat_string, @@ -454,6 +455,11 @@ def test_caesar_cipher(self): self.assertEqual("Lipps_Asvph!", caesar_cipher("Hello_World!", 4)) self.assertEqual("okffng-Qwvb", caesar_cipher("middle-Outz", 2)) +class TestCheckPangram(unittest.TestCase): + def test_check_pangram(self): + self.assertTrue(check_pangram("The quick brown fox jumps over the lazy dog")) + self.assertFalse(check_pangram("The quick brown fox")) + class TestContainString(unittest.TestCase): def test_contain_string(self): From 81a19a775829fdb2d8c968fc1d2b09e3d0eb6d1f Mon Sep 17 00:00:00 2001 From: Reinhold <63191266+reinhold-b@users.noreply.github.com> Date: Fri, 14 Aug 2020 17:20:08 +0200 Subject: [PATCH 023/101] Adding the interpolation search algorithm (#686) * adding interpolation search * requested changes, tests, readme * add to __init__.py * add interpolation_search * add interpolation_search to README * add interpolation_search to README --- README.md | 1 + algorithms/search/__init__.py | 1 + algorithms/search/interpolation_search.py | 53 +++++++++++++++++++++++ tests/test_search.py | 16 ++++++- 4 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 algorithms/search/interpolation_search.py diff --git a/README.md b/README.md index 79c1d0808..c1b369271 100644 --- a/README.md +++ b/README.md @@ -258,6 +258,7 @@ If you want to uninstall algorithms, it is as simple as: - [search_rotate](algorithms/search/search_rotate.py) - [jump_search](algorithms/search/jump_search.py) - [next_greatest_letter](algorithms/search/next_greatest_letter.py) + - [interpolation_search](algorithms/search/interpolation_search.py) - [set](algorithms/set) - [randomized_set](algorithms/set/randomized_set.py) - [set_covering](algorithms/set/set_covering.py) diff --git a/algorithms/search/__init__.py b/algorithms/search/__init__.py index ac5220b38..2586605e1 100644 --- a/algorithms/search/__init__.py +++ b/algorithms/search/__init__.py @@ -9,3 +9,4 @@ from .search_rotate import * from .jump_search import * from .next_greatest_letter import * +from .interpolation_search import * diff --git a/algorithms/search/interpolation_search.py b/algorithms/search/interpolation_search.py new file mode 100644 index 000000000..0759180e3 --- /dev/null +++ b/algorithms/search/interpolation_search.py @@ -0,0 +1,53 @@ +""" +Python implementation of the Interpolation Search algorithm. +Given a sorted array in increasing order, interpolation search calculates +the starting point of its search according to the search key. + +FORMULA: start_pos = low + [ (x - arr[low])*(high - low) / (arr[high] - arr[low]) ] + +Doc: https://en.wikipedia.org/wiki/Interpolation_search + +Time Complexity: O(log2(log2 n)) for average cases, O(n) for the worst case. +The algorithm performs best with uniformly distributed arrays. +""" + +from typing import List + + +def interpolation_search(array: List[int], search_key: int) -> int: + """ + :param array: The array to be searched. + :param search_key: The key to be searched in the array. + + :returns: Index of search_key in array if found, else -1. + + Example + + >>> interpolation_search([1, 10, 12, 15, 20, 41, 55], 20) + 4 + >>> interpolation_search([5, 10, 12, 14, 17, 20, 21], 55) + -1 + + """ + + # highest and lowest index in array + high = len(array) - 1 + low = 0 + + while low <= high and search_key in range(low, array[high] + 1): + # calculate the search position + pos = low + int(((search_key - array[low]) * + (high - low) / (array[high] - array[low]))) + + # if array[pos] equals the search_key then return pos as the index + if search_key == array[pos]: + return pos + # if the search_key is greater than array[pos] restart the search with the + # subarray greater than array[pos] + elif search_key > array[pos]: + low = pos + 1 + # in this case start the search with the subarray smaller than current array[pos] + elif search_key < array[pos]: + high = pos - 1 + + return -1 diff --git a/tests/test_search.py b/tests/test_search.py index 60d8919f7..3f6c23f3e 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -9,11 +9,13 @@ find_min_rotate, find_min_rotate_recur, search_rotate, search_rotate_recur, jump_search, - next_greatest_letter, next_greatest_letter_v1, next_greatest_letter_v2 + next_greatest_letter, next_greatest_letter_v1, next_greatest_letter_v2, + interpolation_search ) import unittest + class TestSuite(unittest.TestCase): def test_first_occurrence(self): @@ -56,7 +58,7 @@ def test_linear_search(self): self.assertEqual(-1, linear_search(array, -1)) def test_search_insert(self): - array = [1,3,5,6] + array = [1, 3, 5, 6] self.assertEqual(2, search_insert(array, 5)) self.assertEqual(1, search_insert(array, 2)) self.assertEqual(4, search_insert(array, 7)) @@ -122,6 +124,16 @@ def test_next_greatest_letter(self): self.assertEqual("c", next_greatest_letter_v1(letters, target)) self.assertEqual("c", next_greatest_letter_v2(letters, target)) + def test_interpolation_search(self): + array = [0, 3, 5, 5, 9, 12, 12, 15, 16, 19, 20] + self.assertEqual(1, interpolation_search(array, 3)) + self.assertEqual(2, interpolation_search(array, 5)) + self.assertEqual(6, interpolation_search(array, 12)) + self.assertEqual(-1, interpolation_search(array, 22)) + self.assertEqual(-1, interpolation_search(array, -10)) + self.assertEqual(10, interpolation_search(array, 20)) + + if __name__ == '__main__': unittest.main() From b928e1fc93c14f9fa656d5908f724d02c8c2e54a Mon Sep 17 00:00:00 2001 From: Iheb Haboubi Date: Fri, 14 Aug 2020 19:34:57 +0100 Subject: [PATCH 024/101] Add binary exponentiation (#687) * Add binary exponentiation * Export power functions * Fix bug in power_recur function * Add tests for power functions in power.py * Fix typo in test * Add power.py link to README.md --- README.md | 1 + algorithms/maths/__init__.py | 1 + algorithms/maths/power.py | 45 ++++++++++++++++++++++++++++++++++++ tests/test_maths.py | 22 ++++++++++++++++++ 4 files changed, 69 insertions(+) create mode 100644 algorithms/maths/power.py diff --git a/README.md b/README.md index c1b369271..f06cbf1ab 100644 --- a/README.md +++ b/README.md @@ -198,6 +198,7 @@ If you want to uninstall algorithms, it is as simple as: - [is_isomorphic](algorithms/map/is_isomorphic.py) - [is_anagram](algorithms/map/is_anagram.py) - [maths](algorithms/maths) + - [power](algorithms/maths/power.py) - [base_conversion](algorithms/maths/base_conversion.py) - [combination](algorithms/maths/combination.py) - [cosine_similarity](algorithms/maths/cosine_similarity.py) diff --git a/algorithms/maths/__init__.py b/algorithms/maths/__init__.py index 7b8a7b1ec..678d923df 100644 --- a/algorithms/maths/__init__.py +++ b/algorithms/maths/__init__.py @@ -18,3 +18,4 @@ from .find_order_simple import * from .find_primitive_root_simple import * from .diffie_hellman_key_exchange import * +from .power import * diff --git a/algorithms/maths/power.py b/algorithms/maths/power.py new file mode 100644 index 000000000..99e694faf --- /dev/null +++ b/algorithms/maths/power.py @@ -0,0 +1,45 @@ +def power(a: int, n: int, r: int = None): + """ + Iterative version of binary exponentiation + + Calculate a ^ n + if r is specified, return the result modulo r + + Time Complexity : O(log(n)) + Space Complexity : O(1) + """ + ans = 1 + while n: + if n & 1: + ans = ans * a + a = a * a + if r: + ans %= r + a %= r + n >>= 1 + return ans + + +def power_recur(a: int, n: int, r: int = None): + """ + Recursive version of binary exponentiation + + Calculate a ^ n + if r is specified, return the result modulo r + + Time Complexity : O(log(n)) + Space Complexity : O(log(n)) + """ + if n == 0: + ans = 1 + elif n == 1: + ans = a + else: + ans = power_recur(a, n // 2, r) + ans = ans * ans + if n % 2: + ans = ans * a + if r: + ans %= r + return ans + diff --git a/tests/test_maths.py b/tests/test_maths.py index c29a8664a..a79c69084 100644 --- a/tests/test_maths.py +++ b/tests/test_maths.py @@ -1,4 +1,5 @@ from algorithms.maths import ( + power,power_recur, int_to_base, base_to_int, decimal_to_binary_ip, euler_totient, @@ -25,6 +26,27 @@ import unittest +class TestPower(unittest.TestCase): + """ + Test for the file power.py + + Arguments: + unittest {[type]} -- [description] + """ + + def test_power(self): + self.assertEqual(8, power(2, 3)) + self.assertEqual(1, power(5, 0)) + self.assertEqual(0, power(10, 3, 5)) + self.assertEqual(280380, power(2265, 1664,465465)) + + def test_power_recur(self): + self.assertEqual(8, power_recur(2, 3)) + self.assertEqual(1, power_recur(5, 0)) + self.assertEqual(0, power_recur(10, 3, 5)) + self.assertEqual(280380, power_recur(2265, 1664,465465)) + + class TestBaseConversion(unittest.TestCase): """ Test for the file base_conversion.py From 07278be93ceb7735493ac35475491686176a9c7b Mon Sep 17 00:00:00 2001 From: chiralevy <51869089+chiralevy@users.noreply.github.com> Date: Sun, 16 Aug 2020 18:04:32 -0400 Subject: [PATCH 025/101] Optimized string is_palindrome main function (#688) Co-authored-by: loan --- algorithms/strings/is_palindrome.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/algorithms/strings/is_palindrome.py b/algorithms/strings/is_palindrome.py index 0318bf5f4..40f404a37 100644 --- a/algorithms/strings/is_palindrome.py +++ b/algorithms/strings/is_palindrome.py @@ -21,9 +21,9 @@ def is_palindrome(s): i = 0 j = len(s)-1 while i < j: - while i < j and not s[i].isalnum(): + while not s[i].isalnum(): i += 1 - while i < j and not s[j].isalnum(): + while not s[j].isalnum(): j -= 1 if s[i].lower() != s[j].lower(): return False From 4b5ccf29e415eff67cf3b947df03da8dfc5bdd49 Mon Sep 17 00:00:00 2001 From: Herpacsi Benedek Date: Sun, 27 Sep 2020 18:19:52 +0200 Subject: [PATCH 026/101] Fixed readme's dfa.py link (#695) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f06cbf1ab..e38640143 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ If you want to uninstall algorithms, it is as simple as: - [move_zeros](algorithms/arrays/move_zeros.py) - [n_sum](algorithms/arrays/n_sum.py) - [automata](algorithms/automata) - - [DFA](algorithms/automata/DFA.py) + - [DFA](algorithms/automata/dfa.py) - [backtrack](algorithms/backtrack) - [general_solution.md](algorithms/backtrack/) - [add_operators](algorithms/backtrack/add_operators.py) @@ -196,7 +196,7 @@ If you want to uninstall algorithms, it is as simple as: - [valid_sudoku](algorithms/map/valid_sudoku.py) - [word_pattern](algorithms/map/word_pattern.py) - [is_isomorphic](algorithms/map/is_isomorphic.py) - - [is_anagram](algorithms/map/is_anagram.py) + - [is_anagram](algorithms/map/is_anagram.py) - [maths](algorithms/maths) - [power](algorithms/maths/power.py) - [base_conversion](algorithms/maths/base_conversion.py) @@ -205,7 +205,7 @@ If you want to uninstall algorithms, it is as simple as: - [decimal_to_binary_ip](algorithms/maths/decimal_to_binary_ip.py) - [euler_totient](algorithms/maths/euler_totient.py) - [extended_gcd](algorithms/maths/extended_gcd.py) - - [factorial](algorithms/maths/factorial.py) + - [factorial](algorithms/maths/factorial.py) - [gcd/lcm](algorithms/maths/gcd.py) - [generate_strobogrammtic](algorithms/maths/generate_strobogrammtic.py) - [is_strobogrammatic](algorithms/maths/is_strobogrammatic.py) From a68ec8aa789b3e5f27bcc2978eb0166ab455218d Mon Sep 17 00:00:00 2001 From: Arijit Gupta Date: Wed, 30 Sep 2020 23:51:19 +0530 Subject: [PATCH 027/101] Adding stooge sort (#697) * Create stooge_sort.py * Update stooge_sort.py * Update README.md * Update __init__.py * Update test_sort.py * Update stooge_sort.py * Update test_sort.py * Update stooge_sort.py --- README.md | 1 + algorithms/sort/__init__.py | 1 + algorithms/sort/stooge_sort.py | 43 ++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 algorithms/sort/stooge_sort.py diff --git a/README.md b/README.md index e38640143..54f6c6a44 100644 --- a/README.md +++ b/README.md @@ -284,6 +284,7 @@ If you want to uninstall algorithms, it is as simple as: - [selection_sort](algorithms/sort/selection_sort.py) - [shell_sort](algorithms/sort/shell_sort.py) - [sort_colors](algorithms/sort/sort_colors.py) + - [stooge_sort](algorithms/sort/stooge_sort.py) - [top_sort](algorithms/sort/top_sort.py) - [wiggle_sort](algorithms/sort/wiggle_sort.py) - [stack](algorithms/stack) diff --git a/algorithms/sort/__init__.py b/algorithms/sort/__init__.py index fceb3bd22..5a870feff 100644 --- a/algorithms/sort/__init__.py +++ b/algorithms/sort/__init__.py @@ -13,6 +13,7 @@ from .top_sort import * from .bucket_sort import * from .shell_sort import * +from .stooge_sort import * from .radix_sort import * from .gnome_sort import * from .cocktail_shaker_sort import * diff --git a/algorithms/sort/stooge_sort.py b/algorithms/sort/stooge_sort.py new file mode 100644 index 000000000..42f8b3bd4 --- /dev/null +++ b/algorithms/sort/stooge_sort.py @@ -0,0 +1,43 @@ +''' + +Stooge Sort +Time Complexity : O(n2.709) +Reference: https://www.geeksforgeeks.org/stooge-sort/ + +''' + + + +def stoogesort(arr, l, h): + if l >= h: + return + + # If first element is smaller + # than last, swap them + if arr[l]>arr[h]: + t = arr[l] + arr[l] = arr[h] + arr[h] = t + + # If there are more than 2 elements in + # the array + if h-l + 1 > 2: + t = (int)((h-l + 1)/3) + + # Recursively sort first 2 / 3 elements + stoogesort(arr, l, (h-t)) + + # Recursively sort last 2 / 3 elements + stoogesort(arr, l + t, (h)) + + # Recursively sort first 2 / 3 elements + # again to confirm + stoogesort(arr, l, (h-t)) + + +if __name__ == "__main__": + array = [1,3,64,5,7,8] + n = len(array) + stoogesort(array, 0, n-1) + for i in range(0, n): + print(array[i], end = ' ') From 38a86ec7e01c90af667a359062203acda9c67896 Mon Sep 17 00:00:00 2001 From: jonathan hanley Date: Thu, 1 Oct 2020 22:00:20 +0100 Subject: [PATCH 028/101] Updated sorting tests to make tests more readable. Added helper function to allow for easier testing of future algorithms --- tests/test_sort.py | 75 ++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 42 deletions(-) diff --git a/tests/test_sort.py b/tests/test_sort.py index 650f33920..c24e20da8 100644 --- a/tests/test_sort.py +++ b/tests/test_sort.py @@ -22,82 +22,73 @@ import unittest +def is_sorted(array): + """ + Helper function to check if the given array is sorted. + :param array: Array to check if sorted + :return: True if sorted in ascending order, else False + """ + for i in range(len(array) - 1): + if array[i] > array[i + 1]: + return False + + return True + + class TestSuite(unittest.TestCase): def test_bogo_sort(self): - self.assertEqual([1, 5, 23], - bogo_sort([1, 23, 5])) + self.assertTrue(is_sorted(bogo_sort([1, 23, 5]))) def test_bitonic_sort(self): - self.assertEqual([1, 2, 3, 5, 23, 57, 65, 1232], - bitonic_sort([1, 3, 2, 5, 65, 23, 57, 1232])) - self.assertEqual([1, 2, 3, 5, 23, 57, 65, 1232], - bitonic_sort([1, 3, 2, 5, 65, 23, 57, 1232],False)) - self.assertEqual([1232, 65, 57, 23, 5, 3, 2, 1], - bitonic_sort([1, 2, 3, 5, 65, 23, 57, 1232],True)) + self.assertTrue(is_sorted(bitonic_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_bubble_sort(self): - self.assertEqual([1, 5, 23, 57, 65, 1232], - bubble_sort([1, 5, 65, 23, 57, 1232])) + self.assertTrue(is_sorted(bubble_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_comb_sort(self): - self.assertEqual([1, 5, 23, 57, 65, 1232], - comb_sort([1, 5, 65, 23, 57, 1232])) + self.assertTrue(is_sorted(comb_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_counting_sort(self): - self.assertEqual([1, 5, 23, 57, 65, 1232], - counting_sort([1, 5, 65, 23, 57, 1232])) - self.assertEqual([-1232, -65, -57, -23, -5, -1], - counting_sort([-1, -5, -65, -23, -57, -1232])) + self.assertTrue(is_sorted(counting_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_cycle_sort(self): - self.assertEqual([1, 5, 23, 57, 65, 1232], - cycle_sort([1, 5, 65, 23, 57, 1232])) + self.assertTrue(is_sorted(cycle_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_heap_sort(self): - self.assertEqual([1, 5, 23, 57, 65, 1232], - max_heap_sort([1, 5, 65, 23, 57, 1232])) - self.assertEqual([1, 5, 23, 57, 65, 1232], - min_heap_sort([1, 5, 65, 23, 57, 1232])) + self.assertTrue(is_sorted(max_heap_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) + + self.assertTrue(is_sorted(min_heap_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_insertion_sort(self): - self.assertEqual([1, 5, 23, 57, 65, 1232], - insertion_sort([1, 5, 65, 23, 57, 1232])) + self.assertTrue(is_sorted(bitonic_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_merge_sort(self): - self.assertEqual([1, 5, 23, 57, 65, 1232], - merge_sort([1, 5, 65, 23, 57, 1232])) + self.assertTrue(is_sorted(merge_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_pancake_sort(self): - self.assertEqual([1, 5, 23, 57, 65, 1232], - pancake_sort([1, 5, 65, 23, 57, 1232])) + self.assertTrue(is_sorted(pancake_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_quick_sort(self): - self.assertEqual([1, 5, 23, 57, 65, 1232], - quick_sort([1, 5, 65, 23, 57, 1232])) + self.assertTrue(is_sorted(quick_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_selection_sort(self): - self.assertEqual([1, 5, 23, 57, 65, 1232], - selection_sort([1, 5, 65, 23, 57, 1232])) + self.assertTrue(is_sorted(selection_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_bucket_sort(self): - self.assertEqual([1, 5, 23, 57, 65, 1232], - bucket_sort([1, 5, 65, 23, 57, 1232])) + self.assertTrue(is_sorted(bucket_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_shell_sort(self): - self.assertEqual([1, 5, 23, 57, 65, 1232], - shell_sort([1, 5, 65, 23, 57, 1232])) + self.assertTrue(is_sorted(shell_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_radix_sort(self): - self.assertEqual([1, 5, 23, 57, 65, 1232], - radix_sort([1, 5, 65, 23, 57, 1232])) + self.assertTrue(is_sorted(radix_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_gnome_sort(self): - self.assertEqual([1, 5, 23, 57, 65, 1232], - gnome_sort([1, 5, 65, 23, 57, 1232])) + self.assertTrue(is_sorted(gnome_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_cocktail_shaker_sort(self): - self.assertEqual([1, 5, 23, 57, 65, 1232], - cocktail_shaker_sort([1, 5, 65, 23, 57, 1232])) + self.assertTrue(is_sorted(cocktail_shaker_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) + class TestTopSort(unittest.TestCase): def setUp(self): From c5d3e2d2766865ed14269c868bf1ad9f6a8a5c4a Mon Sep 17 00:00:00 2001 From: utkarsh Date: Fri, 2 Oct 2020 17:52:23 +0530 Subject: [PATCH 029/101] Added matrix_exponentiation in algorithms/matrix --- README.md | 1 + algorithms/matrix/__init__.py | 2 +- algorithms/matrix/matrix_exponentiation.py | 60 ++++++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 algorithms/matrix/matrix_exponentiation.py diff --git a/README.md b/README.md index 54f6c6a44..4620da2a7 100644 --- a/README.md +++ b/README.md @@ -209,6 +209,7 @@ If you want to uninstall algorithms, it is as simple as: - [gcd/lcm](algorithms/maths/gcd.py) - [generate_strobogrammtic](algorithms/maths/generate_strobogrammtic.py) - [is_strobogrammatic](algorithms/maths/is_strobogrammatic.py) + - [matrix_exponentiation](algorithms/matrix/matrix_exponentiation.py) - [modular_exponential](algorithms/maths/modular_exponential.py) - [next_bigger](algorithms/maths/next_bigger.py) - [next_perfect_square](algorithms/maths/next_perfect_square.py) diff --git a/algorithms/matrix/__init__.py b/algorithms/matrix/__init__.py index 8b1378917..30fc44463 100644 --- a/algorithms/matrix/__init__.py +++ b/algorithms/matrix/__init__.py @@ -1 +1 @@ - +from .matrix_exponentiation import * diff --git a/algorithms/matrix/matrix_exponentiation.py b/algorithms/matrix/matrix_exponentiation.py new file mode 100644 index 000000000..a08c365b3 --- /dev/null +++ b/algorithms/matrix/matrix_exponentiation.py @@ -0,0 +1,60 @@ +def multiply(matA: list, matB: list) -> list: + """ + Multiplies two square matrices matA and matB od size n x n + Time Complexity: O(n^3) + """ + n = len(matA) + matC = [[0 for i in range(n)] for j in range(n)] + + for i in range(n): + for j in range(n): + for k in range(n): + matC[i][j] += matA[i][k] * matB[k][j] + + return matC + +def identity(n: int) -> list: + """ + Returns the Identity matrix of size n x n + Time Complecity: O(n^2) + """ + I = [[0 for i in range(n)] for j in range(n)] + + for i in range(n): + I[i][i] = 1 + + return I + +def matrix_exponentiation(mat: list, n: int) -> list: + """ + Calculates mat^n by repeated squaring + Time Complexity: O(d^3 log(n)) + d: dimesion of the square matrix mat + n: power the matrix is raised to + """ + if n == 0: + return identity(len(mat)) + elif n % 2 == 1: + return multiply(matrix_exponentiation(mat, n - 1), mat) + else: + tmp = matrix_exponentiation(mat, n // 2) + return multiply(tmp, tmp) + +if __name__ == "__main__": + mat = [[1, 0, 2], [2, 1, 0], [0, 2, 1]] + + res0 = matrix_exponentiation(mat, 0) + assert res0 == [[1, 0, 0], [0, 1, 0], [0, 0, 1]] + print(f"{mat}^0 = {res0}") + + res1 = matrix_exponentiation(mat, 1) + assert res1 == [[1, 0, 2], [2, 1, 0], [0, 2, 1]] + print(f"{mat}^1 = {res1}") + + res2 = matrix_exponentiation(mat, 2) + assert res2 == [[1, 4, 4], [4, 1, 4], [4, 4, 1]] + print(f"{mat}^2 = {res2}") + + res5 = matrix_exponentiation(mat, 5) + assert res5 == [[81, 72, 90], [90, 81, 72], [72, 90, 81]] + print(f"{mat}^5 = {res5}") \ No newline at end of file From 0e61ad8dfdbdeb0fd8e8fb129e919cea7afd0b2a Mon Sep 17 00:00:00 2001 From: utkarsh Date: Fri, 2 Oct 2020 17:55:20 +0530 Subject: [PATCH 030/101] Added matrix_exponentiation under matrix in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4620da2a7..618c4fd50 100644 --- a/README.md +++ b/README.md @@ -209,7 +209,6 @@ If you want to uninstall algorithms, it is as simple as: - [gcd/lcm](algorithms/maths/gcd.py) - [generate_strobogrammtic](algorithms/maths/generate_strobogrammtic.py) - [is_strobogrammatic](algorithms/maths/is_strobogrammatic.py) - - [matrix_exponentiation](algorithms/matrix/matrix_exponentiation.py) - [modular_exponential](algorithms/maths/modular_exponential.py) - [next_bigger](algorithms/maths/next_bigger.py) - [next_perfect_square](algorithms/maths/next_perfect_square.py) @@ -231,6 +230,7 @@ If you want to uninstall algorithms, it is as simple as: - [bomb_enemy](algorithms/matrix/bomb_enemy.py) - [copy_transform](algorithms/matrix/copy_transform.py) - [count_paths](algorithms/matrix/count_paths.py) + - [matrix_exponentiation](algorithms/matrix/matrix_exponentiation.py) - [matrix_rotation.txt](algorithms/matrix/matrix_rotation.txt) - [matrix_inversion](algorithms/matrix/matrix_inversion.py) - [matrix_multiplication](algorithms/matrix/multiply.py) From d1e19564377e09bd275aac2505e124f728c055e9 Mon Sep 17 00:00:00 2001 From: utkarsh Date: Fri, 2 Oct 2020 19:41:53 +0530 Subject: [PATCH 031/101] Added tests to tests/test_matrix and modified __init__.py --- algorithms/matrix/__init__.py | 1 - algorithms/matrix/matrix_exponentiation.py | 19 ----------------- tests/test_matrix.py | 24 ++++++++++++++++++++++ 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/algorithms/matrix/__init__.py b/algorithms/matrix/__init__.py index 30fc44463..e69de29bb 100644 --- a/algorithms/matrix/__init__.py +++ b/algorithms/matrix/__init__.py @@ -1 +0,0 @@ -from .matrix_exponentiation import * diff --git a/algorithms/matrix/matrix_exponentiation.py b/algorithms/matrix/matrix_exponentiation.py index a08c365b3..f842bc2fd 100644 --- a/algorithms/matrix/matrix_exponentiation.py +++ b/algorithms/matrix/matrix_exponentiation.py @@ -39,22 +39,3 @@ def matrix_exponentiation(mat: list, n: int) -> list: else: tmp = matrix_exponentiation(mat, n // 2) return multiply(tmp, tmp) - -if __name__ == "__main__": - mat = [[1, 0, 2], [2, 1, 0], [0, 2, 1]] - - res0 = matrix_exponentiation(mat, 0) - assert res0 == [[1, 0, 0], [0, 1, 0], [0, 0, 1]] - print(f"{mat}^0 = {res0}") - - res1 = matrix_exponentiation(mat, 1) - assert res1 == [[1, 0, 2], [2, 1, 0], [0, 2, 1]] - print(f"{mat}^1 = {res1}") - - res2 = matrix_exponentiation(mat, 2) - assert res2 == [[1, 4, 4], [4, 1, 4], [4, 4, 1]] - print(f"{mat}^2 = {res2}") - - res5 = matrix_exponentiation(mat, 5) - assert res5 == [[81, 72, 90], [90, 81, 72], [72, 90, 81]] - print(f"{mat}^5 = {res5}") \ No newline at end of file diff --git a/tests/test_matrix.py b/tests/test_matrix.py index da041e027..88a9b6f73 100644 --- a/tests/test_matrix.py +++ b/tests/test_matrix.py @@ -3,6 +3,7 @@ copy_transform, crout_matrix_decomposition, cholesky_matrix_decomposition, + matrix_exponentiation, matrix_inversion, multiply, rotate_image, @@ -159,6 +160,29 @@ def test_inversion(self): [Fraction(13, 53), Fraction(-22, 53), Fraction(5, 53)]]) +class TestMatrixExponentiation(unittest.TestCase): + """[summary] + Test for the file matrix_exponentiation.py + + Arguments: + unittest {[type]} -- [description] + """ + + def test_matrix_exponentiation(self): + mat = [[1, 0, 2], [2, 1, 0], [0, 2, 1]] + + self.assertEqual(matrix_exponentiation.matrix_exponentiation(mat, 0), + [[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + + self.assertEqual(matrix_exponentiation.matrix_exponentiation(mat, 1), + [[1, 0, 2], [2, 1, 0], [0, 2, 1]]) + + self.assertEqual(matrix_exponentiation.matrix_exponentiation(mat, 2), + [[1, 4, 4], [4, 1, 4], [4, 4, 1]]) + + self.assertEqual(matrix_exponentiation.matrix_exponentiation(mat, 5), + [[81, 72, 90], [90, 81, 72], [72, 90, 81]]) + class TestMultiply(unittest.TestCase): """[summary] From 4ff61dda51a2b02e49699d97aee570b97d5c913e Mon Sep 17 00:00:00 2001 From: HARDIK DADHICH Date: Fri, 2 Oct 2020 20:43:33 +0530 Subject: [PATCH 032/101] fixing link for connected component in readme (#714) Co-authored-by: Hardik dadhich --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bb6c4c09c..b530869c2 100644 --- a/README.md +++ b/README.md @@ -166,7 +166,7 @@ If you want to uninstall algorithms, it is as simple as: - [maximum_flow_dfs](algorithms/graph/maximum_flow_dfs.py) - [all_pairs_shortest_path](algorithms/graph/all_pairs_shortest_path.py) - [bellman_ford](algorithms/graph/bellman_ford.py) - - [Count Connected Components](algoritms/graph/count_connected_number_of_component.py) + - [Count Connected Components](algorithms/graph/count_connected_number_of_component.py) - [heap](algorithms/heap) - [merge_sorted_k_lists](algorithms/heap/merge_sorted_k_lists.py) - [skyline](algorithms/heap/skyline.py) From 476fb94886a109a030a844e8fceead479f73470a Mon Sep 17 00:00:00 2001 From: Rahul Goswami Date: Sun, 4 Oct 2020 20:15:39 +0530 Subject: [PATCH 033/101] update and release v0.1.4 to PyPI --- README.md | 7 +++---- setup.py | 5 +++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index bc9acbae3..7aaafc137 100644 --- a/README.md +++ b/README.md @@ -223,15 +223,14 @@ If you want to uninstall algorithms, it is as simple as: - [hailstone](algorithms/maths/hailstone.py) - [recursive_binomial_coefficient](algorithms/maths/recursive_binomial_coefficient.py) - [find_order](algorithms/maths/find_order_simple.py) - - [find_primitive_root](algorithms/maths/find_primitive_root_simple.py) - - [diffie_hellman_key_exchange](algorithms/maths/diffie_hellman_key_exchange.py) + - [find_primitive_root](algorithms/maths/find_primitive_root_simple.py) + - [diffie_hellman_key_exchange](algorithms/maths/diffie_hellman_key_exchange.py) - [matrix](algorithms/matrix) - [sudoku_validator](algorithms/matrix/sudoku_validator.py) - [bomb_enemy](algorithms/matrix/bomb_enemy.py) - [copy_transform](algorithms/matrix/copy_transform.py) - [count_paths](algorithms/matrix/count_paths.py) - [matrix_exponentiation](algorithms/matrix/matrix_exponentiation.py) - - [matrix_rotation.txt](algorithms/matrix/matrix_rotation.txt) - [matrix_inversion](algorithms/matrix/matrix_inversion.py) - [matrix_multiplication](algorithms/matrix/multiply.py) - [rotate_image](algorithms/matrix/rotate_image.py) @@ -327,7 +326,7 @@ If you want to uninstall algorithms, it is as simple as: - [judge_circle](algorithms/strings/judge_circle.py) - [strong_password](algorithms/strings/strong_password.py) - [caesar_cipher](algorithms/strings/caesar_cipher.py) - - [check_pangram](algorithms/strings/check_pangram.py + - [check_pangram](algorithms/strings/check_pangram.py) - [contain_string](algorithms/strings/contain_string.py) - [count_binary_substring](algorithms/strings/count_binary_substring.py) - [repeat_string](algorithms/strings/repeat_string.py) diff --git a/setup.py b/setup.py index f08f24f14..56099a55c 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ def long_description(): setup(name='algorithms', - version='0.1.0', + version='0.1.4', description='Pythonic Data Structures and Algorithms', long_description=long_description(), long_description_content_type="text/markdown", @@ -19,11 +19,12 @@ def long_description(): author='Algorithms Team & Contributors', author_email="kwk236@gmail.com", license='MIT', - packages=find_packages(), + packages=find_packages(exclude=('tests', 'tests.*')), classifiers=[ 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', ], zip_safe=False) From 7370b8d36af5dc26489f472f9dbe90dd16cc7a79 Mon Sep 17 00:00:00 2001 From: Beata Sudi Date: Fri, 9 Oct 2020 20:17:07 +0200 Subject: [PATCH 034/101] fixing delete_fixup() in red_black_tree (#722) --- algorithms/tree/red_black_tree/red_black_tree.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/algorithms/tree/red_black_tree/red_black_tree.py b/algorithms/tree/red_black_tree/red_black_tree.py index 6ced71ea6..4b3c0d66b 100644 --- a/algorithms/tree/red_black_tree/red_black_tree.py +++ b/algorithms/tree/red_black_tree/red_black_tree.py @@ -241,8 +241,7 @@ def delete_fixup(self, node): node.parent.color = 0 node_brother.right.color = 0 self.left_rotate(node.parent) - node = self.root - break + node = self.root else: node_brother = node.parent.left if node_brother.color == 1: @@ -264,8 +263,7 @@ def delete_fixup(self, node): node.parent.color = 0 node_brother.left.color = 0 self.right_rotate(node.parent) - node = self.root - break + node = self.root node.color = 0 def inorder(self): From bc0b1d33aad8e07bc6f35c54d1b5ace01ea92a9e Mon Sep 17 00:00:00 2001 From: Utkarsh Chaudhary Date: Mon, 12 Oct 2020 06:53:54 +0530 Subject: [PATCH 035/101] Added Modular Inverse (#721) --- README.md | 1 + algorithms/maths/modular_inverse.py | 34 +++++++++++++++++++++++++++++ tests/test_maths.py | 16 ++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 algorithms/maths/modular_inverse.py diff --git a/README.md b/README.md index 7aaafc137..a20a7407b 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,7 @@ If you want to uninstall algorithms, it is as simple as: - [generate_strobogrammtic](algorithms/maths/generate_strobogrammtic.py) - [is_strobogrammatic](algorithms/maths/is_strobogrammatic.py) - [modular_exponential](algorithms/maths/modular_exponential.py) + - [modular_inverse](algorithms/maths/modular_inverse.py) - [next_bigger](algorithms/maths/next_bigger.py) - [next_perfect_square](algorithms/maths/next_perfect_square.py) - [nth_digit](algorithms/maths/nth_digit.py) diff --git a/algorithms/maths/modular_inverse.py b/algorithms/maths/modular_inverse.py new file mode 100644 index 000000000..f53d0f64f --- /dev/null +++ b/algorithms/maths/modular_inverse.py @@ -0,0 +1,34 @@ +# extended_gcd(a, b) modified from +# https://github.com/keon/algorithms/blob/master/algorithms/maths/extended_gcd.py + +def extended_gcd(a: int, b: int) -> [int, int, int]: + """Extended GCD algorithm. + Return s, t, g + such that a * s + b * t = GCD(a, b) + and s and t are co-prime. + """ + + old_s, s = 1, 0 + old_t, t = 0, 1 + old_r, r = a, b + + while r != 0: + quotient = old_r // r + + old_r, r = r, old_r - quotient * r + old_s, s = s, old_s - quotient * s + old_t, t = t, old_t - quotient * t + + return old_s, old_t, old_r + + +def modular_inverse(a: int, m: int) -> int: + """ + Returns x such that a * x = 1 (mod m) + a and m must be coprime + """ + + s, t, g = extended_gcd(a, m) + if g != 1: + raise ValueError("a and m must be coprime") + return s % m diff --git a/tests/test_maths.py b/tests/test_maths.py index a79c69084..766748878 100644 --- a/tests/test_maths.py +++ b/tests/test_maths.py @@ -8,6 +8,7 @@ gcd, lcm, trailing_zero, gcd_bit, gen_strobogrammatic, strobogrammatic_in_range, is_strobogrammatic, is_strobogrammatic2, + modular_inverse, modular_exponential, find_next_square, find_next_square2, prime_check, @@ -166,6 +167,21 @@ def test_is_strobogrammatic2(self): self.assertFalse(is_strobogrammatic2("14")) +class TestModularInverse(unittest.TestCase): + """[summary] + Test for the file modular_Exponential.py + + Arguments: + unittest {[type]} -- [description] + """ + + def test_modular_inverse(self): + # checks if x * x_inv == 1 (mod m) + self.assertEqual(1, 2 * modular_inverse.modular_inverse(2, 19) % 19) + self.assertEqual(1, 53 * modular_inverse.modular_inverse(53, 91) % 91) + self.assertEqual(1, 2 * modular_inverse.modular_inverse(2, 1000000007) % 1000000007) + self.assertRaises(ValueError, modular_inverse.modular_inverse, 2, 20) + class TestModularExponential(unittest.TestCase): """[summary] Test for the file modular_Exponential.py From 30512d9b181fb840dd3d7c39ecb182c257dcdf12 Mon Sep 17 00:00:00 2001 From: Nanak <36790357+Nanak360@users.noreply.github.com> Date: Fri, 23 Oct 2020 08:18:08 +0530 Subject: [PATCH 036/101] Add Krishnamurty Number to Math algorithms (#728) * add Krishnamurthy Number to Math algorithms * tests added for krishnamurthy number, readme update * Update function name of krishnamurthy_number.py Co-authored-by: Nanak Bandyopadhyay --- README.md | 1 + algorithms/maths/__init__.py | 1 + algorithms/maths/krishnamurthy_number.py | 43 ++++++++++++++++++++++++ tests/test_maths.py | 18 +++++++++- 4 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 algorithms/maths/krishnamurthy_number.py diff --git a/README.md b/README.md index a20a7407b..3ba94d3ac 100644 --- a/README.md +++ b/README.md @@ -209,6 +209,7 @@ If you want to uninstall algorithms, it is as simple as: - [gcd/lcm](algorithms/maths/gcd.py) - [generate_strobogrammtic](algorithms/maths/generate_strobogrammtic.py) - [is_strobogrammatic](algorithms/maths/is_strobogrammatic.py) + - [krishnamurthy_number](algorithms/maths/krishnamurthy_number.py) - [modular_exponential](algorithms/maths/modular_exponential.py) - [modular_inverse](algorithms/maths/modular_inverse.py) - [next_bigger](algorithms/maths/next_bigger.py) diff --git a/algorithms/maths/__init__.py b/algorithms/maths/__init__.py index 678d923df..b29b33b1d 100644 --- a/algorithms/maths/__init__.py +++ b/algorithms/maths/__init__.py @@ -19,3 +19,4 @@ from .find_primitive_root_simple import * from .diffie_hellman_key_exchange import * from .power import * +from .krishnamurthy_number import * \ No newline at end of file diff --git a/algorithms/maths/krishnamurthy_number.py b/algorithms/maths/krishnamurthy_number.py new file mode 100644 index 000000000..a0a451ee4 --- /dev/null +++ b/algorithms/maths/krishnamurthy_number.py @@ -0,0 +1,43 @@ +""" +A Krishnamurthy number is a number whose sum total of the factorials of each digit is equal to the number itself. + +Here's what I mean by that: + +"145" is a Krishnamurthy Number because, +1! + 4! + 5! = 1 + 24 + 120 = 145 + +"40585" is also a Krishnamurthy Number. +4! + 0! + 5! + 8! + 5! = 40585 + +"357" or "25965" is NOT a Krishnamurthy Number +3! + 5! + 7! = 6 + 120 + 5040 != 357 + +The following function will check if a number is a Krishnamurthy Number or not and return a boolean value. +""" + + +def find_factorial(n): + fact = 1 + while n != 0: + fact *= n + n -= 1 + return fact + + +def krishnamurthy_number(n): + if n == 0: + return False + sum_of_digits = 0 # will hold sum of FACTORIAL of digits + temp = n + + while temp != 0: + + # get the factorial of of the last digit of n and add it to sum_of_digits + sum_of_digits += find_factorial(temp % 10) + + # replace value of temp by temp/10 + # i.e. will remove the last digit from temp + temp //= 10 + + # returns True if number is krishnamurthy + return (sum_of_digits == n) diff --git a/tests/test_maths.py b/tests/test_maths.py index 766748878..f5586b63f 100644 --- a/tests/test_maths.py +++ b/tests/test_maths.py @@ -21,7 +21,8 @@ cosine_similarity, find_order, find_primitive_root, - alice_private_key, alice_public_key, bob_private_key, bob_public_key, alice_shared_key, bob_shared_key, diffie_hellman_key_exchange + alice_private_key, alice_public_key, bob_private_key, bob_public_key, alice_shared_key, bob_shared_key, diffie_hellman_key_exchange, + krishnamurthy_number, ) import unittest @@ -393,6 +394,21 @@ def test_find_order_simple(self): self.assertEqual(-1, find_order(128, 256)) self.assertEqual(352, find_order(3, 353)) +class TestKrishnamurthyNumber(unittest.TestCase): + """[summary] + Test for the file krishnamurthy_number.py + + Arguments: + unittest {[type]} -- [description] + """ + + def test_krishnamurthy_number(self): + self.assertFalse(krishnamurthy_number(0)) + self.assertTrue(krishnamurthy_number(2)) + self.assertTrue(krishnamurthy_number(1)) + self.assertTrue(krishnamurthy_number(145)) + self.assertTrue(krishnamurthy_number(40585)) + class TestDiffieHellmanKeyExchange(unittest.TestCase): """[summary] From 422b1d16c1dd46a22ce971c6f2670603a22bdf97 Mon Sep 17 00:00:00 2001 From: Nanak <36790357+Nanak360@users.noreply.github.com> Date: Thu, 29 Oct 2020 23:44:57 +0530 Subject: [PATCH 037/101] Add Magic Number to maths algorithms (#730) * add magic number to maths * add Magic Number to maths * Update test_maths.py * Updated variable names and condition statements Co-authored-by: Nanak Bandyopadhyay --- README.md | 1 + algorithms/maths/__init__.py | 3 +- algorithms/maths/magic_number.py | 36 +++++++++++++++++++++++ tests/test_maths.py | 50 +++++++++++++++++++++++--------- 4 files changed, 76 insertions(+), 14 deletions(-) create mode 100644 algorithms/maths/magic_number.py diff --git a/README.md b/README.md index 3ba94d3ac..bcd135f90 100644 --- a/README.md +++ b/README.md @@ -209,6 +209,7 @@ If you want to uninstall algorithms, it is as simple as: - [gcd/lcm](algorithms/maths/gcd.py) - [generate_strobogrammtic](algorithms/maths/generate_strobogrammtic.py) - [is_strobogrammatic](algorithms/maths/is_strobogrammatic.py) + - [magic_number](algorithms/maths/magic_number.py) - [krishnamurthy_number](algorithms/maths/krishnamurthy_number.py) - [modular_exponential](algorithms/maths/modular_exponential.py) - [modular_inverse](algorithms/maths/modular_inverse.py) diff --git a/algorithms/maths/__init__.py b/algorithms/maths/__init__.py index b29b33b1d..92902c66f 100644 --- a/algorithms/maths/__init__.py +++ b/algorithms/maths/__init__.py @@ -19,4 +19,5 @@ from .find_primitive_root_simple import * from .diffie_hellman_key_exchange import * from .power import * -from .krishnamurthy_number import * \ No newline at end of file +from .magic_number import * +from .krishnamurthy_number import * diff --git a/algorithms/maths/magic_number.py b/algorithms/maths/magic_number.py new file mode 100644 index 000000000..fe62bc680 --- /dev/null +++ b/algorithms/maths/magic_number.py @@ -0,0 +1,36 @@ +"""Magic Number +A number is said to be a magic number, +if the sum of its digits are calculated till a single digit recursively +by adding the sum of the digits after every addition. +If the single digit comes out to be 1,then the number is a magic number. + +Example: + Number = 50113 => 5+0+1+1+3=10 => 1+0=1 [This is a Magic Number] + Number = 1234 => 1+2+3+4=10 => 1+0=1 [This is a Magic Number] + Number = 199 => 1+9+9=19 => 1+9=10 => 1+0=1 [This is a Magic Number] + Number = 111 => 1+1+1=3 [This is NOT a Magic Number] + +The following function checks for Magic numbers and returns a Boolean accordingly. +""" + + +def magic_number(n): + total_sum = 0 + + # will end when n becomes 0 + # AND + # sum becomes single digit. + while n > 0 or total_sum > 9: + + # when n becomes 0 but we have a total_sum, + # we update the value of n with the value of the sum digits + if n == 0: + n = total_sum # only when sum of digits isn't single digit + total_sum = 0 + total_sum += n % 10 + n //= 10 + + # Return true if sum becomes 1 + return total_sum == 1 + + diff --git a/tests/test_maths.py b/tests/test_maths.py index f5586b63f..ecceb65e3 100644 --- a/tests/test_maths.py +++ b/tests/test_maths.py @@ -1,5 +1,5 @@ from algorithms.maths import ( - power,power_recur, + power, power_recur, int_to_base, base_to_int, decimal_to_binary_ip, euler_totient, @@ -19,10 +19,11 @@ combination, combination_memo, hailstone, cosine_similarity, + magic_number, find_order, find_primitive_root, - alice_private_key, alice_public_key, bob_private_key, bob_public_key, alice_shared_key, bob_shared_key, diffie_hellman_key_exchange, - krishnamurthy_number, + alice_private_key, alice_public_key, bob_private_key, bob_public_key, alice_shared_key, bob_shared_key, + diffie_hellman_key_exchange, krishnamurthy_number ) import unittest @@ -40,13 +41,13 @@ def test_power(self): self.assertEqual(8, power(2, 3)) self.assertEqual(1, power(5, 0)) self.assertEqual(0, power(10, 3, 5)) - self.assertEqual(280380, power(2265, 1664,465465)) + self.assertEqual(280380, power(2265, 1664, 465465)) def test_power_recur(self): self.assertEqual(8, power_recur(2, 3)) self.assertEqual(1, power_recur(5, 0)) self.assertEqual(0, power_recur(10, 3, 5)) - self.assertEqual(280380, power_recur(2265, 1664,465465)) + self.assertEqual(280380, power_recur(2265, 1664, 465465)) class TestBaseConversion(unittest.TestCase): @@ -136,6 +137,7 @@ def test_gcd_bit(self): self.assertEqual(4, gcd_bit(8, 12)) self.assertEqual(1, gcd(13, 17)) + class TestGenerateStroboGrammatic(unittest.TestCase): """[summary] Test for the file generate_strobogrammatic.py @@ -174,7 +176,7 @@ class TestModularInverse(unittest.TestCase): Arguments: unittest {[type]} -- [description] - """ + """ def test_modular_inverse(self): # checks if x * x_inv == 1 (mod m) @@ -183,6 +185,7 @@ def test_modular_inverse(self): self.assertEqual(1, 2 * modular_inverse.modular_inverse(2, 1000000007) % 1000000007) self.assertRaises(ValueError, modular_inverse.modular_inverse, 2, 20) + class TestModularExponential(unittest.TestCase): """[summary] Test for the file modular_Exponential.py @@ -193,8 +196,8 @@ class TestModularExponential(unittest.TestCase): def test_modular_exponential(self): self.assertEqual(1, modular_exponential(5, 117, 19)) - self.assertEqual(pow(1243, 65321, 10**9 + 7), - modular_exponential(1243, 65321, 10**9 + 7)) + self.assertEqual(pow(1243, 65321, 10 ** 9 + 7), + modular_exponential(1243, 65321, 10 ** 9 + 7)) self.assertEqual(1, modular_exponential(12, 0, 78)) self.assertRaises(ValueError, modular_exponential, 12, -2, 455) @@ -284,7 +287,6 @@ class TestRSA(unittest.TestCase): """ def test_encrypt_decrypt(self): - self.assertEqual(7, decrypt(encrypt(7, 23, 143), 47, 143)) # def test_key_generator(self): # this test takes a while! @@ -326,7 +328,7 @@ def test_factorial(self): self.assertEqual(1, factorial(0)) self.assertEqual(120, factorial(5)) self.assertEqual(3628800, factorial(10)) - self.assertEqual(637816310, factorial(34521, 10**9 + 7)) + self.assertEqual(637816310, factorial(34521, 10 ** 9 + 7)) self.assertRaises(ValueError, factorial, -42) self.assertRaises(ValueError, factorial, 42, -1) @@ -334,7 +336,7 @@ def test_factorial_recur(self): self.assertEqual(1, factorial_recur(0)) self.assertEqual(120, factorial_recur(5)) self.assertEqual(3628800, factorial_recur(10)) - self.assertEqual(637816310, factorial_recur(34521, 10**9 + 7)) + self.assertEqual(637816310, factorial_recur(34521, 10 ** 9 + 7)) self.assertRaises(ValueError, factorial_recur, -42) self.assertRaises(ValueError, factorial_recur, 42, -1) @@ -346,6 +348,7 @@ class TestHailstone(unittest.TestCase): Arguments: unittest {[type]} -- [description] """ + def test_hailstone(self): self.assertEqual([8, 4, 2, 1], hailstone.hailstone(8)) self.assertEqual([10, 5, 16, 8, 4, 2, 1], hailstone.hailstone(10)) @@ -358,6 +361,7 @@ class TestCosineSimilarity(unittest.TestCase): Arguments: unittest {[type]} -- [description] """ + def test_cosine_similarity(self): vec_a = [1, 1, 1] vec_b = [-1, -1, -1] @@ -374,12 +378,13 @@ class TestFindPrimitiveRoot(unittest.TestCase): Arguments: unittest {[type]} -- [description] """ + def test_find_primitive_root_simple(self): self.assertListEqual([0], find_primitive_root(1)) self.assertListEqual([2, 3], find_primitive_root(5)) self.assertListEqual([], find_primitive_root(24)) self.assertListEqual([2, 5, 13, 15, 17, 18, 19, 20, 22, 24, 32, 35], find_primitive_root(37)) - + class TestFindOrder(unittest.TestCase): """[summary] @@ -388,6 +393,7 @@ class TestFindOrder(unittest.TestCase): Arguments: unittest {[type]} -- [description] """ + def test_find_order_simple(self): self.assertEqual(1, find_order(1, 1)) self.assertEqual(6, find_order(3, 7)) @@ -410,6 +416,23 @@ def test_krishnamurthy_number(self): self.assertTrue(krishnamurthy_number(40585)) +class TestMagicNumber(unittest.TestCase): + """[summary] + Test for the file find_order_simple.py + + Arguments: + unittest {[type]} -- [description] + """ + + def test_magic_number(self): + self.assertTrue(magic_number(50113)) + self.assertTrue(magic_number(1234)) + self.assertTrue(magic_number(100)) + self.assertTrue(magic_number(199)) + self.assertFalse(magic_number(2000)) + self.assertFalse(magic_number(500000)) + + class TestDiffieHellmanKeyExchange(unittest.TestCase): """[summary] Test for the file diffie_hellman_key_exchange.py @@ -417,12 +440,13 @@ class TestDiffieHellmanKeyExchange(unittest.TestCase): Arguments: unittest {[type]} -- [description] """ + def test_find_order_simple(self): self.assertFalse(diffie_hellman_key_exchange(3, 6)) self.assertTrue(diffie_hellman_key_exchange(3, 353)) self.assertFalse(diffie_hellman_key_exchange(5, 211)) self.assertTrue(diffie_hellman_key_exchange(11, 971)) + if __name__ == "__main__": unittest.main() - From a0cd48b16f2e7ee6d68ac44d982184cf2552a26f Mon Sep 17 00:00:00 2001 From: ShimoniSinha2019H1030019G <73049785+ShimoniSinha2019H1030019G@users.noreply.github.com> Date: Mon, 2 Nov 2020 09:05:51 +0530 Subject: [PATCH 038/101] Construct full binary tree from preorder and postorder traversal (#736) * Construct full binary tree from preorder and postorder * Construct full binary tree from preorder and postorder * Construct full binary tree from preorder and postorder * Construct full binary tree from preorder and postorder * Construct full binary tree from preorder and postorder * added unit test to test_tree.py and changed to snake case to name cariables and functions * added unit test to test_tree.py and changed to snake case to name cariables and functions * added unit test to test_tree.py and changed to snake case to name cariables and functions --- README.md | 1 + algorithms/tree/__init__.py | 0 .../tree/construct_tree_postorder_preorder.py | 111 ++++++++++++++++++ tests/test_tree.py | 30 +++++ 4 files changed, 142 insertions(+) create mode 100644 algorithms/tree/__init__.py create mode 100644 algorithms/tree/construct_tree_postorder_preorder.py diff --git a/README.md b/README.md index bcd135f90..5fabbbe2e 100644 --- a/README.md +++ b/README.md @@ -375,6 +375,7 @@ If you want to uninstall algorithms, it is as simple as: - [b_tree](algorithms/tree/b_tree.py) - [binary_tree_paths](algorithms/tree/binary_tree_paths.py) - [bin_tree_to_list](algorithms/tree/bin_tree_to_list.py) + - [construct_tree_preorder_postorder](algorithms/tree/construct_tree_postorder_preorder.py) - [deepest_left](algorithms/tree/deepest_left.py) - [invert_tree](algorithms/tree/invert_tree.py) - [is_balanced](algorithms/tree/is_balanced.py) diff --git a/algorithms/tree/__init__.py b/algorithms/tree/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/algorithms/tree/construct_tree_postorder_preorder.py b/algorithms/tree/construct_tree_postorder_preorder.py new file mode 100644 index 000000000..a1bc1688a --- /dev/null +++ b/algorithms/tree/construct_tree_postorder_preorder.py @@ -0,0 +1,111 @@ +""" + Given two arrays representing preorder and postorder traversal of a full + binary tree, construct the binary tree and print the inorder traversal of the + tree. + A full binary tree has either 0 or 2 children. + Algorithm: + 1. Assign the first element of preorder array as root of the tree. + 2. Find the same element in the postorder array and divide the postorder + array into left and right subtree. + 3. Repeat the above steps for all the elements and construct the tree. + Eg: pre[] = {1, 2, 4, 8, 9, 5, 3, 6, 7} + post[] = {8, 9, 4, 5, 2, 6, 7, 3, 1} + Tree: + 1 + / \ + 2 3 + / \ / \ + 4 5 6 7 + / \ + 8 9 + Output: 8 4 9 2 5 1 6 3 7 +""" + +class TreeNode: + + def __init__(self, val, left = None, right = None): + self.val = val + self.left = left + self.right = right + +pre_index = 0 + +def construct_tree_util(pre: list, post: list, low: int, high: int, size: int): + """ + Recursive function that constructs tree from preorder and postorder array. + + preIndex is a global variable that keeps track of the index in preorder + array. + preorder and postorder array are represented are pre[] and post[] respectively. + low and high are the indices for the postorder array. + """ + + global pre_index + + if pre_index == -1: + pre_index = 0 + + + #Base case + if(pre_index >= size or low > high): + return None + + root = TreeNode(pre[pre_index]) + pre_index += 1 + + #If only one element in the subarray return root + if(low == high or pre_index >= size): + return root + + #Find the next element of pre[] in post[] + i = low + while i <= high: + if(pre[pre_index] == post[i]): + break + + i += 1 + + #Use index of element present in postorder to divide postorder array + #to two parts: left subtree and right subtree + if(i <= high): + root.left = construct_tree_util(pre, post, low, i, size) + root.right = construct_tree_util(pre, post, i+1, high, size) + + return root + + +def construct_tree(pre: list, post: list, size: int): + """ + Main Function that will construct the full binary tree from given preorder + and postorder array. + """ + + global pre_index + root = construct_tree_util(pre, post, 0, size-1, size) + + return print_inorder(root) + + + +def print_inorder(root: TreeNode, result = None): + """ + Prints the tree constructed in inorder format + """ + if root is None: + return [] + if result is None: + result = [] + + print_inorder(root.left, result) + result.append(root.val) + print_inorder(root.right, result) + return result + +if __name__ == '__main__': + pre = [1, 2, 4, 5, 3, 6, 7] + post = [4, 5, 2, 6, 7, 3, 1] + size = len(pre) + + result = construct_tree(pre, post, size) + + print(result) diff --git a/tests/test_tree.py b/tests/test_tree.py index 912496f8a..1a2863ac7 100644 --- a/tests/test_tree.py +++ b/tests/test_tree.py @@ -8,6 +8,8 @@ ) from algorithms.tree.b_tree import BTree +from algorithms.tree import construct_tree_postorder_preorder as ctpp + import unittest @@ -107,6 +109,34 @@ def test_deletion_odd_degree(self): self.assertEqual(btree.root.keys, []) self.assertEqual(btree.root.children, []) +class TestConstructTreePreorderPostorder(unittest.TestCase): + def test_construct_tree(self): + + # Test 1 + ctpp.pre_index = 0 + pre1 = [1, 2, 4, 8, 9, 5, 3, 6, 7] + post1 = [8, 9, 4, 5, 2, 6, 7, 3, 1] + size1 = len(pre1) + + self.assertEqual(ctpp.construct_tree(pre1, post1, size1), [8,4,9,2,5,1,6,3,7]) + + # Test 2 + ctpp.pre_index = 0 + pre2 = [1, 2, 4, 5, 3, 6, 7] + post2 = [4, 5, 2, 6, 7, 3, 1] + size2 = len(pre2) + + self.assertEqual(ctpp.construct_tree(pre2, post2, size2), [4,2,5,1,6,3,7]) + + # Test 3 + ctpp.pre_index = 0 + pre3 = [12, 7, 16, 21, 5, 1, 9] + post3 = [16, 21, 7, 1, 9, 5, 12] + size3 = len(pre3) + + self.assertEqual(ctpp.construct_tree(pre3, post3, size3), [16,7,21,12,1,5,9]) + + if __name__ == '__main__': unittest.main() From 9b320bfc972cf8d5e7b8d6b27464ccc527b1da66 Mon Sep 17 00:00:00 2001 From: Erick Lara Date: Sun, 1 Nov 2020 23:47:07 -0600 Subject: [PATCH 039/101] Sort matrix diagonally using heaps. (#665) --- README.md | 1 + algorithms/matrix/sort_matrix_diagonally.py | 77 +++++++++++++++++++++ tests/test_matrix.py | 75 +++++++++++++------- 3 files changed, 128 insertions(+), 25 deletions(-) create mode 100644 algorithms/matrix/sort_matrix_diagonally.py diff --git a/README.md b/README.md index 5fabbbe2e..f08c6d652 100644 --- a/README.md +++ b/README.md @@ -244,6 +244,7 @@ If you want to uninstall algorithms, it is as simple as: - [crout_matrix_decomposition](algorithms/matrix/crout_matrix_decomposition.py) - [cholesky_matrix_decomposition](algorithms/matrix/cholesky_matrix_decomposition.py) - [sum_sub_squares](algorithms/matrix/sum_sub_squares.py) + - [sort_matrix_diagonally](algorithms/matrix/sort_matrix_diagonally.py) - [queues](algorithms/queues) - [max_sliding_window](algorithms/queues/max_sliding_window.py) - [moving_average](algorithms/queues/moving_average.py) diff --git a/algorithms/matrix/sort_matrix_diagonally.py b/algorithms/matrix/sort_matrix_diagonally.py new file mode 100644 index 000000000..4e9e085d6 --- /dev/null +++ b/algorithms/matrix/sort_matrix_diagonally.py @@ -0,0 +1,77 @@ +""" +Given a m * n matrix mat of integers, +sort it diagonally in ascending order +from the top-left to the bottom-right +then return the sorted array. + +mat = [ + [3,3,1,1], + [2,2,1,2], + [1,1,1,2] +] + +Should return: +[ + [1,1,1,1], + [1,2,2,2], + [1,2,3,3] +] +""" + +from heapq import heappush, heappop +from typing import List + + +def sort_diagonally(mat: List[List[int]]) -> List[List[int]]: + # If the input is a vector, return the vector + if len(mat) == 1 or len(mat[0]) == 1: + return mat + + # Rows + columns - 1 + # The -1 helps you to not repeat a column + for i in range(len(mat)+len(mat[0])-1): + # Process the rows + if i+1 < len(mat): + # Initialize heap, set row and column + h = [] + row = len(mat)-(i+1) + col = 0 + + # Traverse diagonally, and add the values to the heap + while row < len(mat): + heappush(h, (mat[row][col])) + row += 1 + col += 1 + + # Sort the diagonal + row = len(mat)-(i+1) + col = 0 + while h: + ele = heappop(h) + mat[row][col] = ele + row += 1 + col += 1 + else: + # Process the columns + # Initialize heap, row and column + h = [] + row = 0 + col = i - (len(mat)-1) + + # Traverse Diagonally + while col < len(mat[0]) and row < len(mat): + heappush(h, (mat[row][col])) + row += 1 + col += 1 + + # Sort the diagonal + row = 0 + col = i - (len(mat)-1) + while h: + ele = heappop(h) + mat[row][col] = ele + row += 1 + col += 1 + + # Return the updated matrix + return mat diff --git a/tests/test_matrix.py b/tests/test_matrix.py index 88a9b6f73..667dc1670 100644 --- a/tests/test_matrix.py +++ b/tests/test_matrix.py @@ -10,7 +10,8 @@ sparse_dot_vector, spiral_traversal, sudoku_validator, - sum_sub_squares + sum_sub_squares, + sort_matrix_diagonally ) import unittest @@ -18,24 +19,24 @@ class TestBombEnemy(unittest.TestCase): def test_3x4(self): grid1 = [ - ["0","E","0","0"], - ["E","0","W","E"], - ["0","E","0","0"] - ] + ["0", "E", "0", "0"], + ["E", "0", "W", "E"], + ["0", "E", "0", "0"] + ] self.assertEqual(3, bomb_enemy.max_killed_enemies(grid1)) grid1 = [ - ["0", "E", "0", "E"], - ["E", "E", "E", "0"], - ["E", "0", "W", "E"], - ["0", "E", "0", "0"] - ] + ["0", "E", "0", "E"], + ["E", "E", "E", "0"], + ["E", "0", "W", "E"], + ["0", "E", "0", "0"] + ] grid2 = [ - ["0", "0", "0", "E"], - ["E", "0", "0", "0"], - ["E", "0", "W", "E"], - ["0", "E", "0", "0"] - ] + ["0", "0", "0", "E"], + ["E", "0", "0", "0"], + ["E", "0", "W", "E"], + ["0", "E", "0", "0"] + ] self.assertEqual(5, bomb_enemy.max_killed_enemies(grid1)) self.assertEqual(3, bomb_enemy.max_killed_enemies(grid2)) @@ -112,7 +113,7 @@ def test_cholesky_matrix_decomposition(self): cholesky_matrix_decomposition.cholesky_decomposition( [[4, 12, -16], [12, 37, -43], [-16, -43, 98]])) - self.assertEqual( None, + self.assertEqual(None, cholesky_matrix_decomposition.cholesky_decomposition( [[4, 12, -8], [12, 4, -43], [-16, -1, 32]])) @@ -123,12 +124,14 @@ def test_cholesky_matrix_decomposition(self): # example taken from https://ece.uwaterloo.ca/~dwharder/NumericalAnalysis/04LinearAlgebra/cholesky/ self.assertEqual([[2.23606797749979, 0.0, 0.0, 0.0], [0.5366563145999494, 2.389979079406345, 0.0, 0.0], - [0.13416407864998736, -0.19749126846635062, 2.818332343581848, 0.0], + [0.13416407864998736, -0.19749126846635062, + 2.818332343581848, 0.0], [-0.2683281572999747, 0.43682390737048743, 0.64657701271919, 3.052723872310221]], cholesky_matrix_decomposition.cholesky_decomposition( [[5, 1.2, 0.3, -0.6], [1.2, 6, -0.4, 0.9], [0.3, -0.4, 8, 1.7], [-0.6, 0.9, 1.7, 10]])) + class TestInversion(unittest.TestCase): """[summary] Test for the file matrix_inversion.py @@ -136,11 +139,13 @@ class TestInversion(unittest.TestCase): Arguments: unittest {[type]} -- [description] """ + def test_inversion(self): from fractions import Fraction m1 = [[1, 1], [1, 2]] - self.assertEqual(matrix_inversion.invert_matrix(m1), [[2, -1], [-1, 1]]) + self.assertEqual(matrix_inversion.invert_matrix(m1), + [[2, -1], [-1, 1]]) m2 = [[1, 2], [3, 4, 5]] self.assertEqual(matrix_inversion.invert_matrix(m2), [[-1]]) @@ -151,15 +156,17 @@ def test_inversion(self): m4 = [[1]] self.assertEqual(matrix_inversion.invert_matrix(m4), [[-3]]) - m5 = [[1, 2, 3] , [4, 5, 6], [7, 8, 9]] + m5 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] self.assertEqual(matrix_inversion.invert_matrix(m5), [[-4]]) m6 = [[3, 5, 1], [2, 5, 0], [1, 9, 8]] self.assertEqual(matrix_inversion.invert_matrix(m6), [[Fraction(40, 53), Fraction(-31, 53), Fraction(-5, 53)], - [Fraction(-16, 53), Fraction(23, 53), Fraction(2, 53)], + [Fraction(-16, 53), Fraction( + 23, 53), Fraction(2, 53)], [Fraction(13, 53), Fraction(-22, 53), Fraction(5, 53)]]) + class TestMatrixExponentiation(unittest.TestCase): """[summary] Test for the file matrix_exponentiation.py @@ -329,6 +336,7 @@ def test_sudoku_validator(self): [3, 0, 0, 4, 8, 1, 1, 7, 9] ])) + class TestSumSubSquares(unittest.TestCase): """[summary] Test for the file sum_sub_squares.py @@ -336,14 +344,31 @@ class TestSumSubSquares(unittest.TestCase): Arguments: unittest {[type]} -- [description] """ + def test_sum_sub_squares(self): - mat = [[1, 1, 1, 1, 1], - [2, 2, 2, 2, 2], - [3, 3, 3, 3, 3], - [4, 4, 4, 4, 4], - [5, 5, 5, 5, 5]] + mat = [[1, 1, 1, 1, 1], + [2, 2, 2, 2, 2], + [3, 3, 3, 3, 3], + [4, 4, 4, 4, 4], + [5, 5, 5, 5, 5]] self.assertEqual(sum_sub_squares.sum_sub_squares(mat, 3), [[18, 18, 18], [27, 27, 27], [36, 36, 36]]) + +class TestSortMatrixDiagonally(unittest.TestCase): + def test_sort_diagonally(self): + mat = [ + [3, 3, 1, 1], + [2, 2, 1, 2], + [1, 1, 1, 2] + ] + + self.assertEqual(sort_matrix_diagonally.sort_diagonally(mat), [ + [1, 1, 1, 1], + [1, 2, 2, 2], + [1, 2, 3, 3] + ]) + + if __name__ == "__main__": unittest.main() From 8ebe8b55c9abfedce1f60c16b9f283dce3133db5 Mon Sep 17 00:00:00 2001 From: ShimoniSinha2019H1030019G <73049785+ShimoniSinha2019H1030019G@users.noreply.github.com> Date: Fri, 6 Nov 2020 23:16:03 +0530 Subject: [PATCH 040/101] Added implementation of fenwick tree (#739) * Added implementation of fenwick tree in the tree folder * Converted into data structure and removed main function. Created a new directory in the tree directory to store the python file. * Converted into data structure and removed main function. Created a new directory in the tree directory to store the python file. * Converted into data structure and removed main function. Created a new directory in the tree directory to store the python file. --- README.md | 2 + algorithms/tree/fenwick_tree/fenwick_tree.py | 77 ++++++++++++++++++++ tests/__init__.py | 0 tests/test_tree.py | 32 ++++++++ 4 files changed, 111 insertions(+) create mode 100644 algorithms/tree/fenwick_tree/fenwick_tree.py delete mode 100644 tests/__init__.py diff --git a/README.md b/README.md index f08c6d652..a883f9f49 100644 --- a/README.md +++ b/README.md @@ -359,6 +359,8 @@ If you want to uninstall algorithms, it is as simple as: - [count_left_node](algorithms/tree/bst/count_left_node.py) - [num_empty](algorithms/tree/bst/num_empty.py) - [height](algorithms/tree/bst/height.py) + - [fenwick_tree](algorithms/tree/fenwick_tree] + - [fenwick_tree](algorithms/tree/fenwick_tree/fenwick_tree.py) - [red_black_tree](algorithms/tree/red_black_tree) - [red_black_tree](algorithms/tree/red_black_tree/red_black_tree.py) - [segment_tree](algorithms/tree/segment_tree) diff --git a/algorithms/tree/fenwick_tree/fenwick_tree.py b/algorithms/tree/fenwick_tree/fenwick_tree.py new file mode 100644 index 000000000..67ec5105e --- /dev/null +++ b/algorithms/tree/fenwick_tree/fenwick_tree.py @@ -0,0 +1,77 @@ +""" +Fenwick Tree / Binary Indexed Tree + +Consider we have an array arr[0 . . . n-1]. We would like to +1. Compute the sum of the first i elements. +2. Modify the value of a specified element of the array arr[i] = x where 0 <= i <= n-1. + +A simple solution is to run a loop from 0 to i-1 and calculate the sum of the elements. To update a value, simply do arr[i] = x. +The first operation takes O(n) time and the second operation takes O(1) time. +Another simple solution is to create an extra array and store the sum of the first i-th elements at the i-th index in this new array. +The sum of a given range can now be calculated in O(1) time, but the update operation takes O(n) time now. +This works well if there are a large number of query operations but a very few number of update operations. + + +There are two solutions that can perform both the query and update operations in O(logn) time. +1. Fenwick Tree +2. Segment Tree + +Compared with Segment Tree, Binary Indexed Tree requires less space and is easier to implement. +""" + +class Fenwick_Tree(object): + def __init__(self, freq): + self.arr = freq + self.n = len(freq) + + def get_sum(self, bit_tree, i): + """ + Returns sum of arr[0..index]. This function assumes that the array is preprocessed and partial sums of array elements are stored in bit_tree[]. + """ + + s = 0 + + # index in bit_tree[] is 1 more than the index in arr[] + i = i+1 + + # Traverse ancestors of bit_tree[index] + while i > 0: + + # Add current element of bit_tree to sum + s += bit_tree[i] + + # Move index to parent node in getSum View + i -= i & (-i) + return s + + def update_bit(self, bit_tree, i, v): + """ + Updates a node in Binary Index Tree (bit_tree) at given index in bit_tree. The given value 'val' is added to bit_tree[i] and all of its ancestors in tree. + """ + + # index in bit_ree[] is 1 more than the index in arr[] + i += 1 + + # Traverse all ancestors and add 'val' + while i <= self.n: + + # Add 'val' to current node of bit_tree + bit_tree[i] += v + + # Update index to that of parent in update View + i += i & (-i) + + + def construct(self): + """ + Constructs and returns a Binary Indexed Tree for given array of size n. + """ + + # Create and initialize bit_ree[] as 0 + bit_tree = [0]*(self.n+1) + + # Store the actual values in bit_ree[] using update() + for i in range(self.n): + self.update_bit(bit_tree, i, self.arr[i]) + + return bit_tree diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/test_tree.py b/tests/test_tree.py index 1a2863ac7..170a931dd 100644 --- a/tests/test_tree.py +++ b/tests/test_tree.py @@ -10,6 +10,8 @@ from algorithms.tree import construct_tree_postorder_preorder as ctpp +from algorithms.tree.fenwick_tree.fenwick_tree import Fenwick_Tree + import unittest @@ -137,6 +139,36 @@ def test_construct_tree(self): self.assertEqual(ctpp.construct_tree(pre3, post3, size3), [16,7,21,12,1,5,9]) +class TestFenwickTree(unittest.TestCase): + def test_construct_tree_with_update_1(self): + freq = [2, 1, 1, 3, 2, 3, 4, 5, 6, 7, 8, 9] + ft = Fenwick_Tree(freq) + bit_tree = ft.construct() + self.assertEqual(12, ft.get_sum(bit_tree, 5)) + + freq[3] += 6 + ft.update_bit(bit_tree, 3, 6) + self.assertEqual(18, ft.get_sum(bit_tree, 5)) + + def test_construct_tree_with_update_2(self): + freq = [1, 2, 3, 4, 5] + ft = Fenwick_Tree(freq) + bit_tree = ft.construct() + self.assertEqual(10, ft.get_sum(bit_tree, 3)) + + freq[3] -= 5 + ft.update_bit(bit_tree, 3, -5) + self.assertEqual(5, ft.get_sum(bit_tree, 3)) + + def test_construct_tree_with_update_3(self): + freq = [2, 1, 4, 6, -1, 5, -32, 0, 1] + ft = Fenwick_Tree(freq) + bit_tree = ft.construct() + self.assertEqual(12, ft.get_sum(bit_tree, 4)) + + freq[2] += 11 + ft.update_bit(bit_tree, 2, 11) + self.assertEqual(23, ft.get_sum(bit_tree, 4)) if __name__ == '__main__': unittest.main() From 2dbf296f558b61798fabd0b5f84fa761b609a68e Mon Sep 17 00:00:00 2001 From: Vignesh Date: Tue, 17 Nov 2020 18:56:44 +0000 Subject: [PATCH 041/101] finding number of digits using logs created with unit tests --- .gitignore | 2 +- algorithms/maths/__init__.py | 1 + algorithms/maths/num_digits.py | 10 ++++++++++ tests/test_maths.py | 15 ++++++++++++++- 4 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 algorithms/maths/num_digits.py diff --git a/.gitignore b/.gitignore index fbcdee784..6ca05bc23 100755 --- a/.gitignore +++ b/.gitignore @@ -13,5 +13,5 @@ __pycache__/ /*.egg # docs build/ - +pythonenv3.8/ .vscode/ diff --git a/algorithms/maths/__init__.py b/algorithms/maths/__init__.py index 7b8a7b1ec..60a371239 100644 --- a/algorithms/maths/__init__.py +++ b/algorithms/maths/__init__.py @@ -18,3 +18,4 @@ from .find_order_simple import * from .find_primitive_root_simple import * from .diffie_hellman_key_exchange import * +from .num_digits import * diff --git a/algorithms/maths/num_digits.py b/algorithms/maths/num_digits.py new file mode 100644 index 000000000..e8373d14b --- /dev/null +++ b/algorithms/maths/num_digits.py @@ -0,0 +1,10 @@ +""" + num_digits() method will return the number of digits of a number in O(1) time using math.log10() method. +""" + +import math + +def num_digits(n): + if(n==0): + return 1; + return int(math.log10(n))+1 diff --git a/tests/test_maths.py b/tests/test_maths.py index c29a8664a..cc270d5ab 100644 --- a/tests/test_maths.py +++ b/tests/test_maths.py @@ -19,7 +19,8 @@ cosine_similarity, find_order, find_primitive_root, - alice_private_key, alice_public_key, bob_private_key, bob_public_key, alice_shared_key, bob_shared_key, diffie_hellman_key_exchange + alice_private_key, alice_public_key, bob_private_key, bob_public_key, alice_shared_key, bob_shared_key, diffie_hellman_key_exchange, + num_digits ) import unittest @@ -369,6 +370,18 @@ def test_find_order_simple(self): self.assertFalse(diffie_hellman_key_exchange(5, 211)) self.assertTrue(diffie_hellman_key_exchange(11, 971)) +class TestNumberOfDigits(unittest.TestCase): + """[summary] + Test for the file num_digits.py + + Arguments: + unittest {[type]} -- [description] + """ + def test_num_digits(self): + self.assertEqual(2,num_digits(12)) + self.assertEqual(5,num_digits(99999)) + self.assertEqual(1,num_digits(8)) + if __name__ == "__main__": unittest.main() From 52e94a5624b2101e00ebcabf280265b9cea9c9ce Mon Sep 17 00:00:00 2001 From: Vignesh Date: Wed, 18 Nov 2020 09:39:16 +0530 Subject: [PATCH 042/101] Update test_maths.py --- tests/test_maths.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_maths.py b/tests/test_maths.py index a8b097bcb..4ed370a1a 100644 --- a/tests/test_maths.py +++ b/tests/test_maths.py @@ -23,7 +23,7 @@ find_order, find_primitive_root, alice_private_key, alice_public_key, bob_private_key, bob_public_key, alice_shared_key, bob_shared_key, diffie_hellman_key_exchange, - num_digits + num_digits, alice_private_key, alice_public_key, bob_private_key, bob_public_key, alice_shared_key, bob_shared_key, diffie_hellman_key_exchange, krishnamurthy_number ) From 6ce5feaa745e2c94ac58f8c9f1853de9138aabc7 Mon Sep 17 00:00:00 2001 From: Aalekh Patel Date: Sun, 22 Nov 2020 09:22:43 -0600 Subject: [PATCH 043/101] Add Polynomials (DS) + Unit tests, and Cycle Index Polynomial for Symmetry Groups (Algorithm) (#741) * Put proper parenthesis for expected output. * Add the Monomial and Polynomial Data Structure for symbolic computation. * Add an algorithm to generate the cycle index polynomial for Symmetry Group on n symbols. * Add multiple Unit Tests for every Polynomial and Monomial operation. * Revert the parenthesis change because it didn't add much value. * Fix typo that introduced a Syntax Error. * Remove future annotations because of incompatibility of postponed evaluation of annotations in Python <= 3.6. * Add record for Polynomial and symmetry group index. Rearrange records to be alphabetical. Co-authored-by: aalekhpatel07 --- README.md | 16 +- algorithms/maths/gcd.py | 4 +- algorithms/maths/polynomial.py | 534 ++++++++++++++++++ .../maths/symmetry_group_cycle_index.py | 162 ++++++ tests/test_monomial.py | 206 +++++++ tests/test_polynomial.py | 175 ++++++ 6 files changed, 1088 insertions(+), 9 deletions(-) create mode 100644 algorithms/maths/polynomial.py create mode 100644 algorithms/maths/symmetry_group_cycle_index.py create mode 100644 tests/test_monomial.py create mode 100644 tests/test_polynomial.py diff --git a/README.md b/README.md index a883f9f49..e192204e2 100644 --- a/README.md +++ b/README.md @@ -198,36 +198,38 @@ If you want to uninstall algorithms, it is as simple as: - [is_isomorphic](algorithms/map/is_isomorphic.py) - [is_anagram](algorithms/map/is_anagram.py) - [maths](algorithms/maths) - - [power](algorithms/maths/power.py) - [base_conversion](algorithms/maths/base_conversion.py) - [combination](algorithms/maths/combination.py) - [cosine_similarity](algorithms/maths/cosine_similarity.py) - [decimal_to_binary_ip](algorithms/maths/decimal_to_binary_ip.py) + - [diffie_hellman_key_exchange](algorithms/maths/diffie_hellman_key_exchange.py) - [euler_totient](algorithms/maths/euler_totient.py) - [extended_gcd](algorithms/maths/extended_gcd.py) - [factorial](algorithms/maths/factorial.py) + - [find_order](algorithms/maths/find_order_simple.py) + - [find_primitive_root](algorithms/maths/find_primitive_root_simple.py) - [gcd/lcm](algorithms/maths/gcd.py) - [generate_strobogrammtic](algorithms/maths/generate_strobogrammtic.py) + - [hailstone](algorithms/maths/hailstone.py) - [is_strobogrammatic](algorithms/maths/is_strobogrammatic.py) - - [magic_number](algorithms/maths/magic_number.py) - [krishnamurthy_number](algorithms/maths/krishnamurthy_number.py) + - [magic_number](algorithms/maths/magic_number.py) - [modular_exponential](algorithms/maths/modular_exponential.py) - [modular_inverse](algorithms/maths/modular_inverse.py) - [next_bigger](algorithms/maths/next_bigger.py) - [next_perfect_square](algorithms/maths/next_perfect_square.py) - [nth_digit](algorithms/maths/nth_digit.py) + - [polynomial](algorithms/maths/polynomial.py) + - [power](algorithms/maths/power.py) - [prime_check](algorithms/maths/prime_check.py) - [primes_sieve_of_eratosthenes](algorithms/maths/primes_sieve_of_eratosthenes.py) - [pythagoras](algorithms/maths/pythagoras.py) - [rabin_miller](algorithms/maths/rabin_miller.py) + - [recursive_binomial_coefficient](algorithms/maths/recursive_binomial_coefficient.py) - [rsa](algorithms/maths/rsa.py) - [sqrt_precision_factor](algorithms/maths/sqrt_precision_factor.py) - [summing_digits](algorithms/maths/summing_digits.py) - - [hailstone](algorithms/maths/hailstone.py) - - [recursive_binomial_coefficient](algorithms/maths/recursive_binomial_coefficient.py) - - [find_order](algorithms/maths/find_order_simple.py) - - [find_primitive_root](algorithms/maths/find_primitive_root_simple.py) - - [diffie_hellman_key_exchange](algorithms/maths/diffie_hellman_key_exchange.py) + - [symmetry_group_cycle_index](algorithms/maths/symmetry_group_cycle_index.py) - [matrix](algorithms/matrix) - [sudoku_validator](algorithms/matrix/sudoku_validator.py) - [bomb_enemy](algorithms/matrix/bomb_enemy.py) diff --git a/algorithms/maths/gcd.py b/algorithms/maths/gcd.py index 002f4fcd3..abc2814dd 100644 --- a/algorithms/maths/gcd.py +++ b/algorithms/maths/gcd.py @@ -32,7 +32,7 @@ def trailing_zero(x): return cnt """ -Given two non-negative integer a and b, +Given two non-negative integer a and b, computes the greatest common divisor of a and b using bitwise operator. """ @@ -42,7 +42,7 @@ def gcd_bit(a, b): a >>= tza b >>= tzb while b: - if a < b: + if a < b: a, b = b, a a -= b a >>= trailing_zero(a) diff --git a/algorithms/maths/polynomial.py b/algorithms/maths/polynomial.py new file mode 100644 index 000000000..55b59dd91 --- /dev/null +++ b/algorithms/maths/polynomial.py @@ -0,0 +1,534 @@ +# from __future__ import annotations + +from fractions import Fraction +from typing import Dict, Union, Set, Iterable +from numbers import Rational +from functools import reduce + + +class Monomial: + """ + A simple Monomial class to + record the details of all variables + that a typical monomial is composed of. + """ + def __init__(self, variables: Dict[int, int], coeff: Union[int, float, Fraction, None]=None) -> None: + ''' + Create a monomial in the given variables: + Examples: + + Monomial({1:1}) = (a_1)^1 + + Monomial({ + 1:3, + 2:2, + 4:1, + 5:0 + }, 12) = 12(a_1)^3(a_2)^2(a_4) + + Monomial({}) = 0 + + Monomial({2:3, 3:-1}, 1.5) = (3/2)(a_2)^3(a_3)^(-1) + + ''' + self.variables = dict() + + if coeff is None: + if len(variables) == 0: + coeff = Fraction(0, 1) + else: + coeff = Fraction(1, 1) + elif coeff == 0: + self.coeff = Fraction(0, 1) + return + + if len(variables) == 0: + self.coeff = Monomial._rationalize_if_possible(coeff) + return + + for i in variables: + if variables[i] != 0: + self.variables[i] = variables[i] + self.coeff = Monomial._rationalize_if_possible(coeff) + + @staticmethod + def _rationalize_if_possible(num): + ''' + A helper for converting numbers + to Fraction only when possible. + ''' + if isinstance(num, Rational): + res = Fraction(num, 1) + return Fraction(res.numerator, res.denominator) + else: + return num + + + # def equal_upto_scalar(self, other: Monomial) -> bool: + def equal_upto_scalar(self, other) -> bool: + """ + Return True if other is a monomial + and is equivalent to self up to a scalar + multiple. + """ + if not isinstance(other, Monomial): + raise ValueError('Can only compare monomials.') + return other.variables == self.variables + + # def __add__(self, other: Union[int, float, Fraction, Monomial]): + def __add__(self, other: Union[int, float, Fraction]): + """ + Define the addition of two + monomials or the addition of + a monomial with an int, float, or a Fraction. + """ + if isinstance(other, int) or isinstance(other, float) or isinstance(other, Fraction): + return self.__add__(Monomial({}, Monomial._rationalize_if_possible(other))) + + if not isinstance(other, Monomial): + raise ValueError('Can only add monomials, ints, floats, or Fractions.') + + if self.variables == other.variables: + mono = {i: self.variables[i] for i in self.variables} + return Monomial(mono, Monomial._rationalize_if_possible(self.coeff + other.coeff)).clean() + + # If they don't share same variables then by the definition, + # if they are added, the result becomes a polynomial and not a monomial. + # Thus, raise ValueError in that case. + + raise ValueError(f'Cannot add {str(other)} to {self.__str__()} because they don\'t have same variables.') + + # def __eq__(self, other: Monomial) -> bool: + def __eq__(self, other) -> bool: + """ + Return True if two monomials + are equal upto a scalar multiple. + """ + return self.equal_upto_scalar(other) and self.coeff == other.coeff + + # def __mul__(self, other: Union[int, float, Fraction, Monomial]) -> Monomial: + def __mul__(self, other: Union[int, float, Fraction]): + """ + Multiply two monomials and merge the variables + in both of them. + + Examples: + + Monomial({1:1}) * Monomial({1: -3, 2: 1}) = (a_1)^(-2)(a_2) + Monomial({3:2}) * 2.5 = (5/2)(a_3)^2 + + """ + if isinstance(other, float) or isinstance(other, int) or isinstance(other, Fraction): + mono = {i: self.variables[i] for i in self.variables} + return Monomial(mono, Monomial._rationalize_if_possible(self.coeff * other)).clean() + + if not isinstance(other, Monomial): + raise ValueError('Can only multiply monomials, ints, floats, or Fractions.') + else: + mono = {i: self.variables[i] for i in self.variables} + for i in other.variables: + if i in mono: + mono[i] += other.variables[i] + else: + mono[i] = other.variables[i] + + temp = dict() + for k in mono: + if mono[k] != 0: + temp[k] = mono[k] + + return Monomial(temp, Monomial._rationalize_if_possible(self.coeff * other.coeff)).clean() + + # def inverse(self) -> Monomial: + def inverse(self): + """ + Compute the inverse of a monomial. + + Examples: + + Monomial({1:1, 2:-1, 3:2}, 2.5).inverse() = Monomial({1:-1, 2:1, 3:-2} ,2/5) + + + """ + mono = {i: self.variables[i] for i in self.variables if self.variables[i] != 0} + for i in mono: + mono[i] *= -1 + if self.coeff == 0: + raise ValueError("Coefficient must not be 0.") + return Monomial(mono, Monomial._rationalize_if_possible(1/self.coeff)).clean() + + # def __truediv__(self, other: Union[int, float, Fraction, Monomial]) -> Monomial: + def __truediv__(self, other: Union[int, float, Fraction]): + """ + Compute the division between two monomials + or a monomial and some other datatype + like int/float/Fraction. + """ + if isinstance(other, int) or isinstance(other, float) or isinstance(other, Fraction): + mono = {i: self.variables[i] for i in self.variables} + if other == 0: + raise ValueError('Cannot divide by 0.') + return Monomial(mono, Monomial._rationalize_if_possible(self.coeff / other)).clean() + + o = other.inverse() + return self.__mul__(o) + + # def __floordiv__(self, other: Union[int, float, Fraction, Monomial]) -> Monomial: + def __floordiv__(self, other: Union[int, float, Fraction]): + """ + For monomials, + floor div is the same as true div. + """ + return self.__truediv__(other) + + # def clone(self) -> Monomial: + def clone(self): + """ + Clone the monomial. + """ + temp_variables = {i: self.variables[i] for i in self.variables} + return Monomial(temp_variables, Monomial._rationalize_if_possible(self.coeff)).clean() + + # def clean(self) -> Monomial: + def clean(self): + """ + Clean the monomial by dropping any variables that have power 0. + """ + temp_variables = {i: self.variables[i] for i in self.variables if self.variables[i] != 0} + return Monomial(temp_variables, Monomial._rationalize_if_possible(self.coeff)) + + # def __sub__(self, other: Union[int, float, Fraction, Monomial]) -> Monomial: + def __sub__(self, other: Union[int, float, Fraction]): + """ + Compute the subtraction + of a monomial and a datatype + such as int, float, Fraction, or Monomial. + """ + if isinstance(other, int) or isinstance(other, float) or isinstance(other, Fraction): + mono = {i: self.variables[i] for i in self.variables if self.variables[i] != 0} + if len(mono) != 0: + raise ValueError('Can only subtract like monomials.') + other_term = Monomial(mono, Monomial._rationalize_if_possible(other)) + return self.__sub__(other_term) + if not isinstance(other, Monomial): + raise ValueError('Can only subtract monomials') + return self.__add__(other.__mul__(Fraction(-1, 1))) + + def __hash__(self) -> int: + """ + Define the hash of a monomial + by the underlying variables. + + If hashing is implemented in O(v*log(v)) + where v represents the number of + variables in the monomial, + then search queries for the + purposes of simplification of a + polynomial can be performed in + O(v*log(v)) as well; much better than + the length of the polynomial. + """ + arr = [] + for i in sorted(self.variables): + if self.variables[i] > 0: + for _ in range(self.variables[i]): + arr.append(i) + return hash(tuple(arr)) + + def all_variables(self) -> Set: + """ + Get the set of all variables + present in the monomial. + """ + return set(sorted(self.variables.keys())) + + def substitute(self, substitutions: Union[int, float, Fraction, Dict[int, Union[int, float, Fraction]]]) -> Fraction: + """ + Substitute the variables in the + monomial for values defined by + the substitutions dictionary. + """ + if isinstance(substitutions, int) or isinstance(substitutions, float) or isinstance(substitutions, Fraction): + substitutions = {v: Monomial._rationalize_if_possible(substitutions) for v in self.all_variables()} + else: + if not self.all_variables().issubset(set(substitutions.keys())): + raise ValueError('Some variables didn\'t receive their values.') + if self.coeff == 0: + return Fraction(0, 1) + ans = Monomial._rationalize_if_possible(self.coeff) + for k in self.variables: + ans *= Monomial._rationalize_if_possible(substitutions[k]**self.variables[k]) + return Monomial._rationalize_if_possible(ans) + + def __str__(self) -> str: + """ + Get a string representation of + the monomial. + """ + if len(self.variables) == 0: + return str(self.coeff) + + result = str(self.coeff) + result += '(' + for i in self.variables: + temp = 'a_{}'.format(str(i)) + if self.variables[i] > 1: + temp = '(' + temp + ')**{}'.format(self.variables[i]) + elif self.variables[i] < 0: + temp = '(' + temp + ')**(-{})'.format(-self.variables[i]) + elif self.variables[i] == 0: + continue + else: + temp = '(' + temp + ')' + result += temp + return result + ')' + + +class Polynomial: + """ + A simple implementation + of a polynomial class that + records the details about two polynomials + that are potentially comprised of multiple + variables. + """ + def __init__(self, monomials: Iterable[Union[int, float, Fraction, Monomial]]) -> None: + ''' + Create a polynomial in the given variables: + Examples: + + Polynomial([ + Monomial({1:1}, 2), + Monomial({2:3, 1:-1}, -1), + math.pi, + Fraction(-1, 2) + ]) = (a_1)^2 + (-1)(a_2)^3(a_1)^(-1) + 2.6415926536 + + Polynomial([]) = 0 + + ''' + self.monomials = set() + for m in monomials: + if any(map(lambda x: isinstance(m, x), [int, float, Fraction])): + self.monomials |= {Monomial({}, m)} + elif isinstance(m, Monomial): + self.monomials |= {m} + else: + raise ValueError('Iterable should have monomials, int, float, or Fraction.') + self.monomials -= {Monomial({}, 0)} + + @staticmethod + def _rationalize_if_possible(num): + ''' + A helper for converting numbers + to Fraction only when possible. + ''' + if isinstance(num, Rational): + res = Fraction(num, 1) + return Fraction(res.numerator, res.denominator) + else: + return num + + + # def __add__(self, other: Union[int, float, Fraction, Monomial, Polynomial]) -> Polynomial: + def __add__(self, other: Union[int, float, Fraction, Monomial]): + """ + Add a given poylnomial to a copy of self. + + """ + if isinstance(other, int) or isinstance(other, float) or isinstance(other, Fraction): + return self.__add__(Monomial({}, Polynomial._rationalize_if_possible(other))) + elif isinstance(other, Monomial): + monos = {m.clone() for m in self.monomials} + + for _own_monos in monos: + if _own_monos.equal_upto_scalar(other): + scalar = _own_monos.coeff + monos -= {_own_monos} + temp_variables = {i: other.variables[i] for i in other.variables} + monos |= {Monomial(temp_variables, Polynomial._rationalize_if_possible(scalar + other.coeff))} + return Polynomial([z for z in monos]) + + monos |= {other.clone()} + return Polynomial([z for z in monos]) + elif isinstance(other, Polynomial): + temp = list(z for z in {m.clone() for m in self.all_monomials()}) + + p = Polynomial(temp) + for o in other.all_monomials(): + p = p.__add__(o.clone()) + return p + else: + raise ValueError('Can only add int, float, Fraction, Monomials, or Polynomials to Polynomials.') + + # def __sub__(self, other: Union[int, float, Fraction, Monomial, Polynomial]) -> Polynomial: + def __sub__(self, other: Union[int, float, Fraction, Monomial]): + """ + Subtract the given polynomial + from a copy of self. + + """ + if isinstance(other, int) or isinstance(other, float) or isinstance(other, Fraction): + return self.__sub__(Monomial({}, Polynomial._rationalize_if_possible(other))) + elif isinstance(other, Monomial): + monos = {m.clone() for m in self.all_monomials()} + for _own_monos in monos: + if _own_monos.equal_upto_scalar(other): + scalar = _own_monos.coeff + monos -= {_own_monos} + temp_variables = {i: other.variables[i] for i in other.variables} + monos |= {Monomial(temp_variables, Polynomial._rationalize_if_possible(scalar - other.coeff))} + return Polynomial([z for z in monos]) + + to_insert = other.clone() + to_insert.coeff *= -1 + + monos |= {to_insert} + return Polynomial([z for z in monos]) + + elif isinstance(other, Polynomial): + p = Polynomial(list(z for z in {m.clone() for m in self.all_monomials()})) + for o in other.all_monomials(): + p = p.__sub__(o.clone()) + return p + + else: + raise ValueError('Can only subtract int, float, Fraction, Monomials, or Polynomials from Polynomials.') + return + + # def __mul__(self, other: Union[int, float, Fraction, Monomial, Polynomial]) -> Polynomial: + def __mul__(self, other: Union[int, float, Fraction, Monomial]): + """ + Multiply a given polynomial + to a copy of self. + """ + if isinstance(other, int) or isinstance(other, float) or isinstance(other, Fraction): + result = Polynomial([]) + monos = {m.clone() for m in self.all_monomials()} + for m in monos: + result = result.__add__(m.clone()*other) + return result + elif isinstance(other, Monomial): + result = Polynomial([]) + monos = {m.clone() for m in self.all_monomials()} + for m in monos: + result = result.__add__(m.clone() * other) + return result + elif isinstance(other, Polynomial): + temp_self = {m.clone() for m in self.all_monomials()} + temp_other = {m.clone() for m in other.all_monomials()} + + result = Polynomial([]) + + for i in temp_self: + for j in temp_other: + result = result.__add__(i * j) + + return result + else: + raise ValueError('Can only multiple int, float, Fraction, Monomials, or Polynomials with Polynomials.') + + # def __floordiv__(self, other: Union[int, float, Fraction, Monomial, Polynomial]) -> Polynomial: + def __floordiv__(self, other: Union[int, float, Fraction, Monomial]): + """ + For Polynomials, floordiv is the same + as truediv. + """ + return self.__truediv__(other) + + # def __truediv__(self, other: Union[int, float, Fraction, Monomial, Polynomial]) -> Polynomial: + def __truediv__(self, other: Union[int, float, Fraction, Monomial]): + """ + For Polynomials, only division by a monomial + is defined. + + TODO: Implement polynomial / polynomial. + """ + if isinstance(other, int) or isinstance(other, float) or isinstance(other, Fraction): + return self.__truediv__( Monomial({}, other) ) + elif isinstance(other, Monomial): + poly_temp = reduce(lambda acc, val: acc + val, map(lambda x: x / other, [z for z in self.all_monomials()]), Polynomial([Monomial({}, 0)])) + return poly_temp + elif isinstance(other, Polynomial): + if Monomial({}, 0) in other.all_monomials(): + if len(other.all_monomials()) == 2: + temp_set = {x for x in other.all_monomials() if x != Monomial({}, 0)} + only = temp_set.pop() + return self.__truediv__(only) + elif len(other.all_monomials()) == 1: + temp_set = {x for x in other.all_monomials()} + only = temp_set.pop() + return self.__truediv__(only) + + raise ValueError('Can only divide a polynomial by an int, float, Fraction, or a Monomial.') + + return + + # def clone(self) -> Polynomial: + def clone(self): + """ + Clone the polynomial. + """ + return Polynomial(list({m.clone() for m in self.all_monomials()})) + + def variables(self) -> Set: + """ + Get all the variables present + in this polynomials. + """ + res = set() + for i in self.all_monomials(): + res |= {j for j in i.variables} + res = list(res) + # res.sort() + return set(res) + + def all_monomials(self) -> Iterable[Monomial]: + """ + Get the monomials of this polynomial. + """ + return {m for m in self.monomials if m != Monomial({}, 0)} + + + def __eq__(self, other) -> bool: + """ + Return True if the other polynomial is the same as + this. + """ + if isinstance(other, int) or isinstance(other, float) or isinstance(other, Fraction): + other_poly = Polynomial([Monomial({}, other)]) + return self.__eq__(other_poly) + elif isinstance(other, Monomial): + return self.__eq__(Polynomial([other])) + elif isinstance(other, Polynomial): + return self.all_monomials() == other.all_monomials() + else: + raise ValueError('Can only compare a polynomial with an int, float, Fraction, Monomial, or another Polynomial.') + + + def subs(self, substitutions: Union[int, float, Fraction, Dict[int, Union[int, float, Fraction]]]) -> Union[int, float, Fraction]: + """ + Get the value after substituting + certain values for the variables + defined in substitutions. + """ + if isinstance(substitutions, int) or isinstance(substitutions, float) or isinstance(substitutions, Fraction): + substitutions = {i: Polynomial._rationalize_if_possible(substitutions) for i in set(self.variables())} + return self.subs(substitutions) + elif not isinstance(substitutions, dict): + raise ValueError('The substitutions should be a dictionary.') + if not self.variables().issubset(set(substitutions.keys())): + raise ValueError('Some variables didn\'t receive their values.') + + ans = 0 + for m in self.all_monomials(): + ans += Polynomial._rationalize_if_possible(m.substitute(substitutions)) + return Polynomial._rationalize_if_possible(ans) + + def __str__(self) -> str: + """ + Get a string representation of + the polynomial. + """ + return ' + '.join(str(m) for m in self.all_monomials() if m.coeff != Fraction(0, 1)) + diff --git a/algorithms/maths/symmetry_group_cycle_index.py b/algorithms/maths/symmetry_group_cycle_index.py new file mode 100644 index 000000000..5f3872ac8 --- /dev/null +++ b/algorithms/maths/symmetry_group_cycle_index.py @@ -0,0 +1,162 @@ +from polynomial import ( Monomial, Polynomial ) +from gcd import lcm +from fractions import Fraction +from typing import Dict, Union + + +""" + The significance of the cycle index (polynomial) of symmetry group + is deeply rooted in counting the number of configurations + of an object excluding those that are symmetric (in terms of permutations). + + For example, the following problem can be solved as a direct + application of the cycle index polynomial of the symmetry + group. + + Note: I came across this problem as a Google's foo.bar challenge at Level 5 + and solved it using a purely Group Theoretic approach. :) + + ----- + + Problem: + + Given positive integers + w, h, and s, + compute the number of distinct 2D + grids of dimensions w x h that contain + entries from {0, 1, ..., s-1}. + Note that two grids are defined + to be equivalent if one can be + obtained from the other by + switching rows and columns + some number of times. + + ----- + + Approach: + + Compute the cycle index (polynomials) + of S_w, and S_h, i.e. the Symmetry + group on w and h symbols respectively. + + Compute the product of the two + cycle indices while combining two + monomials in such a way that + for any pair of cycles c1, and c2 + in the elements of S_w X S_h, + the resultant monomial contains + terms of the form: + $$ x_{lcm(|c1|, |c2|)}^{gcd(|c1|, |c2|)} $$ + + Return the specialization of + the product of cycle indices + at x_i = s (for all the valid i). + + ----- + + Code: + + def solve(w, h, s): + + s1 = get_cycle_index_sym(w) + s2 = get_cycle_index_sym(h) + + result = cycle_product_for_two_polynomials(s1, s2, s) + + return str(result) + +""" + +def cycle_product(m1: Monomial, m2: Monomial) -> Monomial: + """ + Given two monomials (from the + cycle index of a symmetry group), + compute the resultant monomial + in the cartesian product + corresponding to their merging. + """ + assert isinstance(m1, Monomial) and isinstance(m2, Monomial) + A = m1.variables + B = m2.variables + result_variables = dict() + for i in A: + for j in B: + k = lcm(i, j) + g = (i * j) // k + if k in result_variables: + result_variables[k] += A[i] * B[j] * g + else: + result_variables[k] = A[i] * B[j] * g + + return Monomial(result_variables, Fraction(m1.coeff * m2.coeff, 1)) + + +def cycle_product_for_two_polynomials(p1: Polynomial, p2: Polynomial, q: Union[float, int, Fraction]) -> Union[float, int, Fraction]: + """ + Compute the product of + given cycle indices p1, + and p2 and evaluate it at q. + """ + ans = Fraction(0, 1) + for m1 in p1.monomials: + for m2 in p2.monomials: + ans += cycle_product(m1, m2).substitute(q) + + return ans + + +def cycle_index_sym_helper(n: int, memo: Dict[int, Polynomial]) -> Polynomial: + """ + A helper for the dp-style evaluation + of the cycle index. + + The recurrence is given in: + https://en.wikipedia.org/wiki/Cycle_index#Symmetric_group_Sn + + """ + if n in memo: + return memo[n] + ans = Polynomial([Monomial({}, Fraction(0, 1))]) + for t in range(1, n+1): + ans = ans.__add__(Polynomial([Monomial({t: 1}, Fraction(1, 1))]) * cycle_index_sym_helper(n-t, memo)) + ans *= Fraction(1, n) + memo[n] = ans + return memo[n] + + +def get_cycle_index_sym(n: int) -> Polynomial: + """ + Compute the cycle index + of S_n, i.e. the symmetry + group of n symbols. + + """ + if n < 0: + raise ValueError('n should be a non-negative integer.') + + memo = { + 0: Polynomial([ + Monomial({}, Fraction(1, 1)) + ]), + 1: Polynomial([ + Monomial({1:1}, Fraction(1, 1)) + ]), + 2: Polynomial([ + Monomial({1: 2}, Fraction(1, 2)), + Monomial({2: 1}, Fraction(1, 2)) + ]), + 3: Polynomial([ + Monomial({1: 3}, Fraction(1, 6)), + Monomial({1: 1, 2: 1}, Fraction(1, 2)), + Monomial({3:1}, Fraction(1, 3)) + ]), + 4: Polynomial([ + Monomial({1:4}, Fraction(1, 24)), + Monomial({2:1, 1:2},Fraction(1, 4)), + Monomial({3:1, 1:1}, Fraction(1, 3)), + Monomial({2:2}, Fraction(1, 8)), + Monomial({4:1}, Fraction(1, 4)), + ]) + } + result = cycle_index_sym_helper(n, memo) + return result diff --git a/tests/test_monomial.py b/tests/test_monomial.py new file mode 100644 index 000000000..8e4667d83 --- /dev/null +++ b/tests/test_monomial.py @@ -0,0 +1,206 @@ +from algorithms.maths.polynomial import Monomial +from fractions import Fraction +import math + + +import unittest + +class TestSuite(unittest.TestCase): + + def setUp(self): + self.m1 = Monomial({}) + self.m2 = Monomial({1: 1}, 2) + self.m3 = Monomial({1: 2, 2: -1}, 1.5) + self.m4 = Monomial({1: 1, 2: 2, 3: -2}, 3) + self.m5 = Monomial({2: 1, 3: 0}, Fraction(2, 3)) + self.m6 = Monomial({1: 0, 2: 0, 3: 0}, -2.27) + self.m7 = Monomial({1: 2, 7: 2}, -math.pi) + self.m8 = Monomial({150: 5, 170: 2, 10000:3}, 0) + self.m9 = 2 + self.m10 = math.pi + self.m11 = Fraction(3, 8) + self.m12 = 0 + self.m13 = Monomial({1: 1}, -2) + self.m14 = Monomial({1: 2}, 3) + self.m15 = Monomial({1: 1}, 3) + self.m16 = Monomial({1: 2, 7: 2}, math.pi) + self.m17 = Monomial({1: -1}) + + def test_monomial_addition(self): + + # Monomials with different underlying variables or + # even different power of those variables must not be added! + self.assertRaises(ValueError, lambda x, y: x + y, self.m1, self.m2) + self.assertRaises(ValueError, lambda x, y: x + y, self.m2, self.m3) + self.assertRaises(ValueError, lambda x, y: x + y, self.m2, self.m14) + + # Additive inverses of each other should produce the zero monomial. + self.assertEqual(self.m13 + self.m2, self.m1) + + # Zero monomial + Zero monomial = Zero monomial + self.assertEqual(self.m1 + self.m1, self.m1) + + # Coefficient float. + self.assertEqual(self.m7 + self.m7, Monomial({1: 2, 7: 2}, -2*math.pi)) + + # Coefficient 0 so should equal the zero monomial. + self.assertEqual(self.m8, self.m1) + + # The constant term cannot be added to any monomial + # that has any variables. + self.assertRaises(ValueError, lambda x, y: x + y, self.m2, self.m9) + + # Any literal cannot be added to a Monomial. However, a monomial + # can be added to any int, float, Fraction, or Monomial. + + # So 2 + Monomial is raises TypeError but Monomial + 2 may work fine! + self.assertRaises(TypeError, lambda x, y: x + y, self.m9, self.m2) + + # Any constant added to a zero monomial produces + # a monomial. + self.assertEqual(self.m1 + self.m9, Monomial({}, 2)) + self.assertEqual(self.m1 + self.m12, Monomial({}, 0)) + + return + + def test_monomial_subtraction(self): + + # Monomials with different underlying variables or + # even different power of those variables must not be subtracted! + self.assertRaises(ValueError, lambda x, y: x - y, self.m1, self.m2) + self.assertRaises(ValueError, lambda x, y: x - y, self.m2, self.m3) + self.assertRaises(ValueError, lambda x, y: x - y, self.m2, self.m14) + + # Additive inverses of each other should produce the zero monomial. + self.assertEqual(self.m2 - self.m2, self.m1) + self.assertEqual(self.m2 - self.m2, Monomial({}, 0)) + + # Zero monomial - Zero monomial = Zero monomial + self.assertEqual(self.m1 - self.m1, self.m1) + + # Coefficient int. + self.assertEqual(self.m2 - self.m15, Monomial({1: 1}, -1)) + + # Coefficient float. + self.assertEqual(self.m16 - self.m7, Monomial({1: 2, 7: 2}, 2*math.pi)) + + + # The constant term cannot be added to any monomial + # that has any variables. + self.assertRaises(ValueError, lambda x, y: x - y, self.m2, self.m9) + + # Any literal cannot be added to a Monomial. However, a monomial + # can be added to any int, float, Fraction, or Monomial. + + # So 2 + Monomial is raises TypeError but Monomial + 2 may work fine! + self.assertRaises(TypeError, lambda x, y: x - y, self.m9, self.m2) + + # Any constant added to a zero monomial produces + # a monomial. + self.assertEqual(self.m1 - self.m9, Monomial({}, -2)) + self.assertEqual(self.m1 - self.m12, Monomial({}, 0)) + + return + + def test_monomial_multiplication(self): + + # Usual multiplication. + # The positive and negative powers of the same variable + # should cancel out. + self.assertEqual(self.m2 * self.m13, Monomial({1: 2}, -4)) + self.assertEqual(self.m2 * self.m17, Monomial({}, 2)) + + # A coefficient of zero should make the product zero. + # Zero monomial * any int, float, Fraction, or Monomial = Zero monomial + self.assertEqual(self.m8 * self.m5, self.m1) + self.assertEqual(self.m1 * self.m2, self.m1) + + # Test usual float multiplication. + self.assertEqual(self.m7 * self.m3, Monomial({1: 4, 2: -1, 7: 2}, -1.5*math.pi)) + + return + + def test_monomial_inverse(self): + + # The Zero monomial is not invertible. + self.assertRaises(ValueError, lambda x: x.inverse(), self.m1) + self.assertRaises(ValueError, lambda x: x.inverse(), self.m8) + self.assertRaises(ValueError, lambda x: x.inverse(), Monomial({},self.m12)) + + # Check some inverses. + self.assertEqual(self.m7.inverse(), Monomial({1: -2, 7: -2}, -1/math.pi)) + + # Doesn't matter if the coefficient is Fraction or float. + # Both should be treated as same. + self.assertEqual(self.m5.inverse(), Monomial({2: -1}, Fraction(3, 2))) + self.assertEqual(self.m5.inverse(), Monomial({2: -1}, 1.5)) + + # Should work fine without variables too! + self.assertTrue(self.m6.inverse(), Monomial({}, Fraction(-100, 227))) + self.assertEqual(self.m6.inverse(), Monomial({}, -1/2.27)) + return + + def test_monomial_division(self): + # Any monomial divided by the Zero Monomial should raise a ValueError. + self.assertRaises(ValueError, lambda x, y: x.__truediv__(y), self.m2, self.m1) + self.assertRaises(ValueError, lambda x, y: x.__truediv__(y), self.m2, self.m8) + self.assertRaises(ValueError, lambda x, y: x.__truediv__(y), self.m2, self.m12) + + # Test some usual cases. + self.assertEqual(self.m7 / self.m3, Monomial({2: 1, 7: 2}, -2*math.pi/3)) + self.assertEqual(self.m14 / self.m13, Monomial({1: 1}) * Fraction(-3, 2)) + + return + + def test_monomial_substitution(self): + # Test with int. + self.assertAlmostEqual(self.m7.substitute(2), -16*math.pi, delta=1e-9) + # Test with float. + self.assertAlmostEqual(self.m7.substitute(1.5), (1.5 ** 4)* -math.pi, delta=1e-9) + # Test with Fraction. + self.assertAlmostEqual(self.m7.substitute(Fraction(-1, 2)), (Fraction(-1, 2) ** 4)*-math.pi, delta=1e-9) + # Test with a complete substitution map. + self.assertAlmostEqual(self.m7.substitute({1: 3, 7: 0}), (3 ** 2) * (0 ** 2) * -math.pi, delta=1e-9) + # Test with a more than complete substitution map. + self.assertAlmostEqual(self.m7.substitute({1: 3, 7: 0, 2: 2}), (3 ** 2) * (0 ** 2) * -math.pi, delta=1e-9) + + # Should raise a ValueError if not enough variables are supplied! + self.assertRaises(ValueError, lambda x, y: x.substitute(y), self.m7, {1: 3, 2: 2}) + self.assertRaises(ValueError, lambda x, y: x.substitute(y), self.m7, {2: 2}) + + # The zero monomial always gives zero upon substitution. + self.assertEqual(self.m8.substitute(2), 0) + self.assertEqual(self.m8.substitute({1231: 2, 1: 2}), 0) + + return + + def test_monomial_all_variables(self): + + # Any variable with zero power should not exist in the set + # of variables. + self.assertEqual(self.m5.all_variables(), {2}) + self.assertEqual(self.m6.all_variables(), set()) + + # The zero monomial should output empty set. + self.assertEqual(self.m8.all_variables(), set()) + + return + + def test_monomial_clone(self): + + # A monomial should produce its copy + # with same underlying variable dictionary + # and same coefficient. + self.assertEqual(self.m3, self.m3.clone()) + + # The zero monomial is identified and + # always clones to itself. + self.assertEqual(self.m1, self.m8.clone()) + self.assertEqual(self.m1, self.m1.clone()) + self.assertEqual(self.m8, self.m1.clone()) + self.assertEqual(self.m8, self.m8.clone()) + return + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/test_polynomial.py b/tests/test_polynomial.py new file mode 100644 index 000000000..4ba2ba6b7 --- /dev/null +++ b/tests/test_polynomial.py @@ -0,0 +1,175 @@ +from algorithms.maths.polynomial import ( + Polynomial, + Monomial +) +from fractions import Fraction +import math + + +import unittest + +class TestSuite(unittest.TestCase): + + def setUp(self): + self.p0 = Polynomial([ + Monomial({}) + ]) + self.p1 = Polynomial([ + Monomial({}), Monomial({}) + ]) + self.p2 = Polynomial([ + Monomial({1: 1}, 2) + ]) + self.p3 = Polynomial([ + Monomial({1: 1}, 2), + Monomial({1: 2, 2: -1}, 1.5) + ]) + self.p4 = Polynomial([ + Monomial({2: 1, 3: 0}, Fraction(2, 3)), + Monomial({1: -1, 3: 2}, math.pi), + Monomial({1: -1, 3: 2}, 1) + ]) + self.p5 = Polynomial([ + Monomial({150: 5, 170: 2, 10000:3}, 0), + Monomial({1: -1, 3: 2}, 1), + ]) + self.p6 = Polynomial([ + 2, + -3, + Fraction(1, 7), + 2**math.pi, + Monomial({2: 3, 3: 1}, 1.25) + ]) + self.p7 = Polynomial([ + Monomial({1: 1}, -2), + Monomial({1: 2, 2: -1}, -1.5) + ]) + + self.m1 = Monomial({1: 2, 2: 3}, -1) + + return + + def test_polynomial_addition(self): + + # The zero polynomials should add up to + # itselves only. + self.assertEqual(self.p0 + self.p1, self.p0) + self.assertEqual(self.p0 + self.p1, self.p1) + + # Additive inverses should add up to the + # zero polynomial. + self.assertEqual(self.p3 + self.p7, self.p0) + self.assertEqual(self.p3 + self.p7, self.p1) + + # Like terms should combine. + # The order of monomials should not matter. + self.assertEqual(self.p2 + self.p3, Polynomial([ + Monomial({1: 1}, 4), + Monomial({1: 2, 2: -1}, 1.5) + ])) + self.assertEqual(self.p2 + self.p3, Polynomial([ + Monomial({1: 2, 2: -1}, 1.5), + Monomial({1: 1}, 4), + ])) + + # Another typical computation. + self.assertEqual(self.p5 + self.p6, Polynomial([ + Monomial({}, 7.96783496993343), + Monomial({2: 3, 3: 1}, 1.25), + Monomial({1: -1, 3: 2}) + ])) + + return + + def test_polynomial_subtraction(self): + + self.assertEqual(self.p3 - self.p2, Polynomial([ + Monomial({1: 2, 2: -1}, 1.5) + ])) + + self.assertEqual(self.p3 - self.p3, Polynomial([])) + + self.assertEqual(self.p2 - self.p3, Polynomial([ + Monomial({1: 2, 2: -1}, -1.5) + ])) + + pass + + def test_polynomial_multiplication(self): + self.assertEqual(self.p0 * self.p2, Polynomial([])) + self.assertEqual(self.p1 * self.p2, Polynomial([])) + + self.assertEqual(self.p2 * self.p3, Polynomial([ + Monomial({1: 2}, 4), + Monomial({1: 3, 2: -1}, Fraction(3, 1)) + ])) + return + + def test_polynomial_division(self): + + # Should raise a ValueError if the divisor is not a monomial + # or a polynomial with only one term. + self.assertRaises(ValueError, lambda x, y: x / y, self.p5, self.p3) + self.assertRaises(ValueError, lambda x, y: x / y, self.p6, self.p4) + + self.assertEqual(self.p3 / self.p2, Polynomial([ + Monomial({}, 1), + Monomial({1: 1, 2: -1}, 0.75) + ])) + self.assertEqual(self.p7 / self.m1, Polynomial([ + Monomial({1: -1, 2: -3}, 2), + Monomial({1: 0, 2: -4}, 1.5) + ])) + self.assertEqual(self.p7 / self.m1, Polynomial([ + Monomial({1: -1, 2: -3}, 2), + Monomial({2: -4}, 1.5) + ])) + return + + def test_polynomial_variables(self): + # The zero polynomial has no variables. + + self.assertEqual(self.p0.variables(), set()) + self.assertEqual(self.p1.variables(), set()) + + # The total variables are the union of the variables + # from the monomials. + self.assertEqual(self.p4.variables(), {1, 2, 3}) + + # The monomials with coefficient 0 should be dropped. + self.assertEqual(self.p5.variables(), {1, 3}) + return + + def test_polynomial_subs(self): + # Anything substitued in the zero polynomial + # should evaluate to 0. + self.assertEqual(self.p1.subs(2), 0) + self.assertEqual(self.p0.subs(-101231), 0) + + # Should raise a ValueError if not enough variables are supplied. + self.assertRaises(ValueError, lambda x, y: x.subs(y), self.p4, {1: 3, 2: 2}) + self.assertRaises(ValueError, lambda x, y: x.subs(y), self.p4, {}) + + # Should work fine if a complete subsitution map is provided. + self.assertAlmostEqual(self.p4.subs({1: 1, 2: 1, 3: 1}), (1 + math.pi + Fraction(2, 3)), delta=1e-9) + # Should work fine if more than enough substitutions are provided. + self.assertAlmostEqual(self.p4.subs({1: 1, 2: 1, 3: 1, 4: 1}), (1 + math.pi + Fraction(2, 3)), delta=1e-9) + return + + def test_polynomial_clone(self): + + # The zero polynomial always clones to itself. + self.assertEqual(self.p0.clone(), self.p0) + self.assertEqual(self.p1.clone(), self.p0) + self.assertEqual(self.p0.clone(), self.p1) + self.assertEqual(self.p1.clone(), self.p1) + + # The polynomial should clone nicely. + self.assertEqual(self.p4.clone(), self.p4) + + # The monomial with a zero coefficient should be dropped + # in the clone. + self.assertEqual(self.p5.clone(), Polynomial([ + Monomial({1: -1, 3: 2}, 1) + ])) + return \ No newline at end of file From 162024fad3fcbad047ac26bed121dd6ce8da8d06 Mon Sep 17 00:00:00 2001 From: Aditya Bennur <46860321+TheCodeYoda@users.noreply.github.com> Date: Fri, 4 Dec 2020 20:40:04 +0530 Subject: [PATCH 044/101] Adding k_factor.py in dp folder in add_K_FactorofStrings branch (#743) * Adding k_factor.py in dp folder in add_K_FactorofStrings branch * Added tests for K_factor of strings * updated README by adding kfactor into dp section * updated __init__.py in dp folder Co-authored-by: Aditya --- README.md | 1 + algorithms/dp/__init__.py | 1 + algorithms/dp/k_factor.py | 68 +++++++++++++++++++++++++++++++++++++++ tests/test_dp.py | 29 ++++++++++++++++- 4 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 algorithms/dp/k_factor.py diff --git a/README.md b/README.md index e192204e2..c14e68468 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,7 @@ If you want to uninstall algorithms, it is as simple as: - [word_break](algorithms/dp/word_break.py) - [fibonacci](algorithms/dp/fib.py) - [hosoya triangle](algorithms/dp/hosoya_triangle.py) + - [K-Factor_strings](algorithms/dp/k_factor.py) - [graph](algorithms/graph) - [check_bipartite](algorithms/graph/check_bipartite.py) - [strongly_connected](algorithms/graph/check_digraph_strongly_connected.py) diff --git a/algorithms/dp/__init__.py b/algorithms/dp/__init__.py index c96a9b334..4492a9c9b 100644 --- a/algorithms/dp/__init__.py +++ b/algorithms/dp/__init__.py @@ -19,3 +19,4 @@ from .rod_cut import * from .word_break import * from .int_divide import * +from .k_factor import * diff --git a/algorithms/dp/k_factor.py b/algorithms/dp/k_factor.py new file mode 100644 index 000000000..2fd23d18b --- /dev/null +++ b/algorithms/dp/k_factor.py @@ -0,0 +1,68 @@ +'''The K factor of a string is defined as the number of times 'abba' appears as a substring. +Given two numbers N and k,​ find the number of strings of length N with 'K factor' = k. + +The algorithms is as follows: + +dp[n][k] will be a 4 element array, wherein each element can be the number of strings of length n and 'K factor' = k which belong to the criteria represented by that index: + + dp[n][k][0] can be the number of strings of length n and K-factor = k which end with substring 'a' + dp[n][k][1] can be the number of strings of length n and K-factor = k which end with substring 'ab' + dp[n][k][2] can be the number of strings of length n and K-factor = k which end with substring 'abb' + dp[n][k][3] can be the number of strings of length n and K-factor = k which end with anything other than the above substrings (anything other than 'a' 'ab' 'abb') + +Example inputs + +n=4 k=1 no of strings = 1 +n=7 k=1 no of strings = 70302 +n=10 k=2 no of strings = 74357 + +''' + +def find_k_factor(n,k): + dp=[[[0 for i in range(4)]for j in range((n-1)//3+2)]for k in range(n+1)] + if(3*k+1>n): + return 0 + #base cases + dp[1][0][0]=1; + dp[1][0][1]=0; + dp[1][0][2]=0; + dp[1][0][3]=25; + + for i in range(2,n+1): + for j in range((n-1)//3+2): + if(j==0): + #adding a at the end + dp[i][j][0]=dp[i-1][j][0]+dp[i-1][j][1]+dp[i-1][j][3] + + #adding b at the end + dp[i][j][1]=dp[i-1][j][0] + dp[i][j][2]=dp[i-1][j][1] + + #adding any other lowercase character + dp[i][j][3]=dp[i-1][j][0]*24+dp[i-1][j][1]*24+dp[i-1][j][2]*25+dp[i-1][j][3]*25 + + elif(3*j+1 Date: Sun, 17 Jan 2021 17:32:30 +0530 Subject: [PATCH 045/101] Create longest_sum_contigous_array.py --- .../arrays/longest_sum_contigous_array.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 algorithms/arrays/longest_sum_contigous_array.py diff --git a/algorithms/arrays/longest_sum_contigous_array.py b/algorithms/arrays/longest_sum_contigous_array.py new file mode 100644 index 000000000..40cd5a448 --- /dev/null +++ b/algorithms/arrays/longest_sum_contigous_array.py @@ -0,0 +1,64 @@ +""" +Find the sum of contiguous subarray within a one-dimensional array of numbers which has the largest sum. + +The idea of algorithm is to look for all positive contiguous segments of the array (max_ending_here is used for this). +And keep track of maximum sum contiguous segment among all positive segments (max_so_far is used for this). +Each time we get a positive sum compare it with max_so_far and update max_so_far if it is greater than max_so_far + +Example) +Let input array = [-2,-3,4,-1,-2,1,5,3] + +max_so_far = max_ending_here = 0 + +for i=0, a[0] = -2 +max_ending_here = max_ending_here + (-2) +Set max_ending_here = 0 because max_ending_here < 0 + +for i=1, a[1] = -3 +max_ending_here = max_ending_here + (-3) +Set max_ending_here = 0 because max_ending_here < 0 + +for i=2, a[2] = 4 +max_ending_here = max_ending_here + (4) +max_ending_here = 4 +max_so_far is updated to 4 because max_ending_here greater +than max_so_far which was 0 till now + +for i=3, a[3] = -1 +max_ending_here = max_ending_here + (-1) +max_ending_here = 3 + +for i=4, a[4] = -2 +max_ending_here = max_ending_here + (-2) +max_ending_here = 1 + +for i=5, a[5] = 1 +max_ending_here = max_ending_here + (1) +max_ending_here = 2 + +for i=6, a[6] = 5 +max_ending_here = max_ending_here + (5) +max_ending_here = 7 +max_so_far is updated to 7 because max_ending_here is +greater than max_so_far + +for i=7, a[7] = -3 +max_ending_here = max_ending_here + (-3) +max_ending_here = 4 + +4+(-1)+(-2)+1+5 = 7 +hence,maximum contiguous array sum is 7 +""" +def max_subarray_sum(a,size): + + max_so_far = 0 + max_ending_here = 0 + + for i in range(0, size): + max_ending_here = max_ending_here + a[i] + if max_ending_here < 0: + max_ending_here = 0 + elif (max_so_far < max_ending_here): + max_so_far = max_ending_here + + return max_so_far From 77cfed5c2a67cb81d3f3cf3650f190231d0b932e Mon Sep 17 00:00:00 2001 From: Divyanshmandhan-1 <70142854+Divyanshmandhan-1@users.noreply.github.com> Date: Mon, 18 Jan 2021 10:50:44 +0530 Subject: [PATCH 046/101] add ternary search --- algorithms/search/ternary_search.py | 37 +++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 algorithms/search/ternary_search.py diff --git a/algorithms/search/ternary_search.py b/algorithms/search/ternary_search.py new file mode 100644 index 000000000..b7d36fb21 --- /dev/null +++ b/algorithms/search/ternary_search.py @@ -0,0 +1,37 @@ +""" +Ternary search is a divide and conquer algorithm that can be used to find an element in an array. +It is similar to binary search where we divide the array into two parts but in this algorithm, +we divide the given array into three parts and determine which has the key (searched element). +We can divide the array into three parts by taking mid1 and mid2. +Initially, l and r will be equal to 0 and n-1 respectively, where n is the length of the array. +mid1 = l + (r-l)/3 +mid2 = r – (r-l)/3 + +Note: Array needs to be sorted to perform ternary search on it. +T(N) = O(log3(N)) +log3 = log base 3 +""" +def ternary_search(l, r, key, arr): + while r >= l: + + mid1 = l + (r-l) // 3 + mid2 = r - (r-l) // 3 + + if key == arr[mid1]: + return mid1 + if key == mid2: + return mid2 + + if key < arr[mid1]: + # key lies between l and mid1 + r = mid1 - 1 + elif key > arr[mid2]: + # key lies between mid2 and r + l = mid2 + 1 + else: + # key lies between mid1 and mid2 + l = mid1 + 1 + r = mid2 - 1 + + # key not found + return -1 From 7a1e7250d248cb098db6b06e28df50564006f806 Mon Sep 17 00:00:00 2001 From: Divyanshmandhan-1 <70142854+Divyanshmandhan-1@users.noreply.github.com> Date: Mon, 18 Jan 2021 10:59:07 +0530 Subject: [PATCH 047/101] Delete longest_sum_contigous_array.py --- .../arrays/longest_sum_contigous_array.py | 64 ------------------- 1 file changed, 64 deletions(-) delete mode 100644 algorithms/arrays/longest_sum_contigous_array.py diff --git a/algorithms/arrays/longest_sum_contigous_array.py b/algorithms/arrays/longest_sum_contigous_array.py deleted file mode 100644 index 40cd5a448..000000000 --- a/algorithms/arrays/longest_sum_contigous_array.py +++ /dev/null @@ -1,64 +0,0 @@ -""" -Find the sum of contiguous subarray within a one-dimensional array of numbers which has the largest sum. - -The idea of algorithm is to look for all positive contiguous segments of the array (max_ending_here is used for this). -And keep track of maximum sum contiguous segment among all positive segments (max_so_far is used for this). -Each time we get a positive sum compare it with max_so_far and update max_so_far if it is greater than max_so_far - -Example) -Let input array = [-2,-3,4,-1,-2,1,5,3] - -max_so_far = max_ending_here = 0 - -for i=0, a[0] = -2 -max_ending_here = max_ending_here + (-2) -Set max_ending_here = 0 because max_ending_here < 0 - -for i=1, a[1] = -3 -max_ending_here = max_ending_here + (-3) -Set max_ending_here = 0 because max_ending_here < 0 - -for i=2, a[2] = 4 -max_ending_here = max_ending_here + (4) -max_ending_here = 4 -max_so_far is updated to 4 because max_ending_here greater -than max_so_far which was 0 till now - -for i=3, a[3] = -1 -max_ending_here = max_ending_here + (-1) -max_ending_here = 3 - -for i=4, a[4] = -2 -max_ending_here = max_ending_here + (-2) -max_ending_here = 1 - -for i=5, a[5] = 1 -max_ending_here = max_ending_here + (1) -max_ending_here = 2 - -for i=6, a[6] = 5 -max_ending_here = max_ending_here + (5) -max_ending_here = 7 -max_so_far is updated to 7 because max_ending_here is -greater than max_so_far - -for i=7, a[7] = -3 -max_ending_here = max_ending_here + (-3) -max_ending_here = 4 - -4+(-1)+(-2)+1+5 = 7 -hence,maximum contiguous array sum is 7 -""" -def max_subarray_sum(a,size): - - max_so_far = 0 - max_ending_here = 0 - - for i in range(0, size): - max_ending_here = max_ending_here + a[i] - if max_ending_here < 0: - max_ending_here = 0 - elif (max_so_far < max_ending_here): - max_so_far = max_ending_here - - return max_so_far From 2c04f10b6322754e3071b9bd0106f0785b3c64ce Mon Sep 17 00:00:00 2001 From: Vignesh Date: Sun, 24 Jan 2021 14:50:28 +0000 Subject: [PATCH 048/101] num_digits for negative values --- algorithms/maths/num_digits.py | 1 + tests/test_maths.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/algorithms/maths/num_digits.py b/algorithms/maths/num_digits.py index e8373d14b..93f0e20f3 100644 --- a/algorithms/maths/num_digits.py +++ b/algorithms/maths/num_digits.py @@ -5,6 +5,7 @@ import math def num_digits(n): + n=abs(n) if(n==0): return 1; return int(math.log10(n))+1 diff --git a/tests/test_maths.py b/tests/test_maths.py index 4ed370a1a..2b1e69315 100644 --- a/tests/test_maths.py +++ b/tests/test_maths.py @@ -461,6 +461,9 @@ def test_num_digits(self): self.assertEqual(2,num_digits(12)) self.assertEqual(5,num_digits(99999)) self.assertEqual(1,num_digits(8)) + self.assertEqual(1,num_digits(0)) + self.assertEqual(1,num_digits(-5)) + self.assertEqual(3,num_digits(-254)) if __name__ == "__main__": From fec7a5b09f696caaf042ea32358666239fa72a35 Mon Sep 17 00:00:00 2001 From: Vignesh Date: Sun, 24 Jan 2021 14:52:39 +0000 Subject: [PATCH 049/101] .venv folder added in gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 6ca05bc23..5152c3d1b 100755 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ __pycache__/ build/ pythonenv3.8/ .vscode/ +# Ignoring the virtual Environment when using GitHub Codespaces +.venv/ \ No newline at end of file From ef63e5329a02022d1f2a0dea27af9b367f6e2cf8 Mon Sep 17 00:00:00 2001 From: Divyanshmandhan-1 <70142854+Divyanshmandhan-1@users.noreply.github.com> Date: Thu, 28 Jan 2021 12:50:58 +0530 Subject: [PATCH 050/101] added test cases for ternary search --- algorithms/search/__init__.py | 1 + tests/test_search.py | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/algorithms/search/__init__.py b/algorithms/search/__init__.py index 2586605e1..2226e4b5d 100644 --- a/algorithms/search/__init__.py +++ b/algorithms/search/__init__.py @@ -1,4 +1,5 @@ from .binary_search import * +from .ternary_search import * from .first_occurrence import * from .last_occurrence import * from .linear_search import * diff --git a/tests/test_search.py b/tests/test_search.py index 3f6c23f3e..d1ea8225d 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -1,5 +1,6 @@ from algorithms.search import ( binary_search, binary_search_recur, + ternary_search, first_occurrence, last_occurrence, linear_search, @@ -41,6 +42,15 @@ def test_binary_search(self): self.assertEqual(11, binary_search_recur(array, 0, 11, 6)) self.assertEqual(-1, binary_search_recur(array, 0, 11, 7)) self.assertEqual(-1, binary_search_recur(array, 0, 11, -1)) + + def test_ternary_search(self): + array = [1, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 6] + self.assertEqual(10, ternary_search(0, 11, 5, array)) + self.assertEqual(3, ternary_search(0, 10, 3, array)) + self.assertEqual(-1, ternary_search(0, 10, 5, array)) + self.assertEqual(-1, ternary_search(0, 11, 7, array)) + self.assertEqual(-1, ternary_search(0, 11, -1, array)) + def test_last_occurrence(self): array = [1, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 6, 6, 6] From 68be49ca496afe1f084bfc93e67247a27f0519fa Mon Sep 17 00:00:00 2001 From: Renstrom <48918700+Renstrom@users.noreply.github.com> Date: Sun, 7 Mar 2021 00:34:33 +0100 Subject: [PATCH 051/101] Add 1-sparse-recovery streaming algorithm (#762) * feat:(first draft for the misra gries algorithm) #1 * feat:(Added examples and changed to correct name) #1 * feat:(Added init file for testing) #2 * test:(Added tests for misras_gries function) #2 * feat:(add 1-sparse recovery algorithm) #7 * Add finalized 1-sparse-recovery algorithm * Renamed sparse function name to work with import * Tests added for 1-sparse-recovery function * Tests added for 1-sparse-recovery function Co-authored-by: callmeGoldenboy --- README.md | 2 + algorithms/streaming/__init__.py | 1 + algorithms/streaming/one_sparse_recovery.py | 61 +++++++++++++++++++++ tests/test_streaming.py | 15 +++++ 4 files changed, 79 insertions(+) create mode 100644 algorithms/streaming/__init__.py create mode 100644 algorithms/streaming/one_sparse_recovery.py create mode 100644 tests/test_streaming.py diff --git a/README.md b/README.md index c14e68468..ffe602b01 100644 --- a/README.md +++ b/README.md @@ -305,6 +305,8 @@ If you want to uninstall algorithms, it is as simple as: - [is_consecutive](algorithms/stack/is_consecutive.py) - [remove_min](algorithms/stack/remove_min.py) - [is_sorted](algorithms/stack/is_sorted.py) +- [streaming](algorithms/streaming) + - [1-sparse-recovery](algorithms/streaming/one_sparse_recovery.py) - [strings](algorithms/strings) - [fizzbuzz](algorithms/strings/fizzbuzz.py) - [delete_reoccurring](algorithms/strings/delete_reoccurring.py) diff --git a/algorithms/streaming/__init__.py b/algorithms/streaming/__init__.py new file mode 100644 index 000000000..ba7a84ea8 --- /dev/null +++ b/algorithms/streaming/__init__.py @@ -0,0 +1 @@ +from .one_sparse_recovery import * \ No newline at end of file diff --git a/algorithms/streaming/one_sparse_recovery.py b/algorithms/streaming/one_sparse_recovery.py new file mode 100644 index 000000000..084a9f7b8 --- /dev/null +++ b/algorithms/streaming/one_sparse_recovery.py @@ -0,0 +1,61 @@ +""" Non-negative 1-sparse recovery problem. This algorithm assumes we have a non negative dynamic stream. +Given a stream of tuples, where each tuple contains a number and a sign (+/-), it check if the stream is 1-sparse, meaning if the elements +in the stream cancel eacheother out in such a way that ther is only a unique number at the end. + +Examples: +#1 +Input: [(4,'+'), (2,'+'),(2,'-'),(4,'+'),(3,'+'),(3,'-')], +Output: 4 +Comment: Since 2 and 3 gets removed. +#2 +Input: [(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'+')] +Output: 2 +Comment: No other numbers present +#3 +Input: [(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'+'),(1,'+')] +Output: None +Comment: Not 1-sparse +""" + +def one_sparse(array): + sum_signs = 0 + bitsum = [0]*32 + sum_values = 0 + for val,sign in array: + if sign == "+": + sum_signs += 1 + sum_values += val + else: + sum_signs -= 1 + sum_values -= val + + _get_bit_sum(bitsum,val,sign) + + if sum_signs > 0 and _check_every_number_in_bitsum(bitsum,sum_signs): + return int(sum_values/sum_signs) + else: + return None + +#Helper function to check that every entry in the list is either 0 or the same as the +#sum of signs +def _check_every_number_in_bitsum(bitsum,sum_signs): + for val in bitsum: + if val != 0 and val != sum_signs : + return False + return True + +# Adds bit representation value to bitsum array +def _get_bit_sum(bitsum,val,sign): + i = 0 + if sign == "+": + while(val): + bitsum[i] += val & 1 + i +=1 + val >>=1 + else : + while(val): + bitsum[i] -= val & 1 + i +=1 + val >>=1 + + diff --git a/tests/test_streaming.py b/tests/test_streaming.py new file mode 100644 index 000000000..8662480e6 --- /dev/null +++ b/tests/test_streaming.py @@ -0,0 +1,15 @@ +from algorithms.streaming import ( + one_sparse +) +import unittest + +class TestOneSparse(unittest.TestCase): + def test_one_sparse_correct(self): + self.assertEqual(4,one_sparse([(4,'+'), (2,'+'),(2,'-'),(4,'+'),(3,'+'),(3,'-')])) + self.assertEqual(2,one_sparse([(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'+')])) + + + def test_one_sparse_incorrect(self): + self.assertEqual(None,one_sparse([(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'+'),(1,'+')])) #Two values remaining + self.assertEqual(None,one_sparse([(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'-'),(2,'-'),(2,'-'),(2,'-')])) # No values remaining + self.assertEqual(None,one_sparse([(2,'+'),(2,'+'),(4,'+'),(4,'+')])) # Bitsum sum of sign is inccorect \ No newline at end of file From 3074384b640291e68114584ffbc13b14ff311a07 Mon Sep 17 00:00:00 2001 From: Lazar Cerovic Date: Sun, 7 Mar 2021 00:40:28 +0100 Subject: [PATCH 052/101] Fix gcd (#760) * fix: add input argument checks and lcm/gcd can handle negative numbers * test: add new tests for gcd/lcm to test new fixes --- algorithms/maths/gcd.py | 14 +++++++++++++- tests/test_maths.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/algorithms/maths/gcd.py b/algorithms/maths/gcd.py index abc2814dd..35af7f118 100644 --- a/algorithms/maths/gcd.py +++ b/algorithms/maths/gcd.py @@ -1,7 +1,19 @@ def gcd(a, b): """Computes the greatest common divisor of integers a and b using Euclid's Algorithm. + gcd{𝑎,𝑏}=gcd{−𝑎,𝑏}=gcd{𝑎,−𝑏}=gcd{−𝑎,−𝑏} + See proof: https://proofwiki.org/wiki/GCD_for_Negative_Integers """ + a_int = isinstance(a, int) + b_int = isinstance(b, int) + a = abs(a) + b = abs(b) + if not(a_int or b_int): + raise ValueError("Input arguments are not integers") + + if (a == 0) or (b == 0) : + raise ValueError("One or more input arguments equals zero") + while b != 0: a, b = b, a % b return a @@ -9,7 +21,7 @@ def gcd(a, b): def lcm(a, b): """Computes the lowest common multiple of integers a and b.""" - return a * b / gcd(a, b) + return abs(a) * abs(b) / gcd(a, b) """ diff --git a/tests/test_maths.py b/tests/test_maths.py index 2b1e69315..95db71088 100644 --- a/tests/test_maths.py +++ b/tests/test_maths.py @@ -29,6 +29,7 @@ ) import unittest +import pytest class TestPower(unittest.TestCase): @@ -128,8 +129,37 @@ def test_gcd(self): self.assertEqual(4, gcd(8, 12)) self.assertEqual(1, gcd(13, 17)) + def test_gcd_non_integer_input(self): + with pytest.raises(ValueError, match=r"Input arguments are not integers"): + gcd(1.0, 5) + gcd(5, 6.7) + gcd(33.8649, 6.12312312) + + def test_gcd_zero_input(self): + with pytest.raises(ValueError, match=r"One or more input arguments equals zero"): + gcd(0, 12) + gcd(12, 0) + gcd(0, 0) + + def test_gcd_negative_input(self): + self.assertEqual(1, gcd(-13, -17)) + self.assertEqual(4, gcd(-8, 12)) + self.assertEqual(8, gcd(24, -16)) + def test_lcm(self): self.assertEqual(24, lcm(8, 12)) + self.assertEqual(5767, lcm(73, 79)) + + def test_lcm_negative_numbers(self): + self.assertEqual(24, lcm(-8, -12)) + self.assertEqual(5767, lcm(73, -79)) + self.assertEqual(1, lcm(-1, 1)) + + def test_lcm_zero_input(self): + with pytest.raises(ValueError, match=r"One or more input arguments equals zero"): + lcm(0, 12) + lcm(12, 0) + lcm(0, 0) def test_trailing_zero(self): self.assertEqual(1, trailing_zero(34)) @@ -140,6 +170,7 @@ def test_gcd_bit(self): self.assertEqual(1, gcd(13, 17)) + class TestGenerateStroboGrammatic(unittest.TestCase): """[summary] Test for the file generate_strobogrammatic.py From e685f8d3798ffeb0f4b91c4e40390748b8fc7c47 Mon Sep 17 00:00:00 2001 From: Natan Date: Mon, 8 Mar 2021 02:38:31 +0100 Subject: [PATCH 053/101] Add misra-gries streaming algorithm (#765) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat:(first draft for the misra gries algorithm) #1 * feat:(Added examples and changed to correct name) #1 * feat:(Added init file for testing) #2 * test:(Added tests for misras_gries function) #2 * add misra-gries reference * add correct reference to misra_gries * add misra_gries import Co-authored-by: Anders Renström Co-authored-by: Keon --- README.md | 1 + algorithms/streaming/__init__.py | 3 +- algorithms/streaming/misra_gries.py | 49 +++++++++++++++++++++++++++++ tests/test_streaming.py | 19 +++++++++-- 4 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 algorithms/streaming/misra_gries.py diff --git a/README.md b/README.md index ffe602b01..6280ff5af 100644 --- a/README.md +++ b/README.md @@ -307,6 +307,7 @@ If you want to uninstall algorithms, it is as simple as: - [is_sorted](algorithms/stack/is_sorted.py) - [streaming](algorithms/streaming) - [1-sparse-recovery](algorithms/streaming/one_sparse_recovery.py) + - [misra-gries](algorithms/streaming/misra_gries.py) - [strings](algorithms/strings) - [fizzbuzz](algorithms/strings/fizzbuzz.py) - [delete_reoccurring](algorithms/strings/delete_reoccurring.py) diff --git a/algorithms/streaming/__init__.py b/algorithms/streaming/__init__.py index ba7a84ea8..3e764d2c3 100644 --- a/algorithms/streaming/__init__.py +++ b/algorithms/streaming/__init__.py @@ -1 +1,2 @@ -from .one_sparse_recovery import * \ No newline at end of file +from .one_sparse_recovery import * +from .misra_gries import * diff --git a/algorithms/streaming/misra_gries.py b/algorithms/streaming/misra_gries.py new file mode 100644 index 000000000..31339094d --- /dev/null +++ b/algorithms/streaming/misra_gries.py @@ -0,0 +1,49 @@ + +""" +Implementation of the Misra-Gries algorithm. +Given a list of items and a value k, it returns the every item in the list that appears at least n/k times, where n is the length of the array +By default, k is set to 2, solving the majority problem. +For the majority problem, this algorithm only guarantees that if there is an element that appears more than n/2 times, it will be outputed. If there +is no such element, any arbitrary element is returned by the algorithm. Therefore, we need to iterate through again at the end. But since we have filtred +out the suspects, the memory complexity is significantly lower than it would be to create counter for every element in the list. + +For example: +Input misras_gries([1,4,4,4,5,4,4]) +Output {'4':5} +Input misras_gries([0,0,0,1,1,1,1]) +Output {'1':4} +Input misras_gries([0,0,0,0,1,1,1,2,2],3) +Output {'0':4,'1':3} +Input misras_gries([0,0,0,1,1,1] +Output None +""" +def misras_gries(array,k=2): + keys = {} + for i in range(len(array)): + val = str(array[i]) + if val in keys: + keys[val] = keys[val] + 1 + + elif len(keys) < k - 1: + keys[val] = 1 + + else: + for key in list(keys): + keys[key] = keys[key] - 1 + if keys[key] == 0: + del keys[key] + + suspects = keys.keys() + frequencies = {} + for suspect in suspects: + freq = _count_frequency(array,int(suspect)) + if freq >= len(array) / k: + frequencies[suspect] = freq + + return frequencies if len(frequencies) > 0 else None + + +def _count_frequency(array,element): + return array.count(element) + + diff --git a/tests/test_streaming.py b/tests/test_streaming.py index 8662480e6..701b5f4ce 100644 --- a/tests/test_streaming.py +++ b/tests/test_streaming.py @@ -1,7 +1,22 @@ +from algorithms.streaming.misra_gries import ( + misras_gries, +) from algorithms.streaming import ( one_sparse ) -import unittest +import unittest + + +class TestMisraGreis(unittest.TestCase): + def test_misra_correct(self): + self.assertEqual({'4':5},misras_gries([1,4,4,4,5,4,4])) + self.assertEqual({'1':4},misras_gries([0,0,0,1,1,1,1])) + self.assertEqual({'0':4,'1':3},misras_gries([0,0,0,0,1,1,1,2,2],3)) + + def test_misra_incorrect(self): + self.assertEqual(None,misras_gries([1,2,5,4,5,4,4,5,4,4,5])) + self.assertEqual(None,misras_gries([0,0,0,2,1,1,1])) + self.assertEqual(None,misras_gries([0,0,0,1,1,1])) class TestOneSparse(unittest.TestCase): def test_one_sparse_correct(self): @@ -12,4 +27,4 @@ def test_one_sparse_correct(self): def test_one_sparse_incorrect(self): self.assertEqual(None,one_sparse([(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'+'),(1,'+')])) #Two values remaining self.assertEqual(None,one_sparse([(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'-'),(2,'-'),(2,'-'),(2,'-')])) # No values remaining - self.assertEqual(None,one_sparse([(2,'+'),(2,'+'),(4,'+'),(4,'+')])) # Bitsum sum of sign is inccorect \ No newline at end of file + self.assertEqual(None,one_sparse([(2,'+'),(2,'+'),(4,'+'),(4,'+')])) # Bitsum sum of sign is inccorect From 6adcb360cd11108ae30dfca945e85fd3d35a869c Mon Sep 17 00:00:00 2001 From: Caroline Borg Date: Wed, 10 Mar 2021 11:24:36 +0100 Subject: [PATCH 054/101] Fix #756 (#771) Co-authored-by: Caroline Borg --- README.md | 1 + algorithms/strings/__init__.py | 1 + algorithms/strings/panagram.py | 36 +++++++++++++++ tests/test_strings.py | 81 +++++++++++++++++++++++++++++++++- 4 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 algorithms/strings/panagram.py diff --git a/README.md b/README.md index 6280ff5af..948fe6c4c 100644 --- a/README.md +++ b/README.md @@ -348,6 +348,7 @@ If you want to uninstall algorithms, it is as simple as: - [atbash_cipher](algorithms/strings/atbash_cipher.py) - [longest_palindromic_substring](algorithms/strings/longest_palindromic_substring.py) - [knuth_morris_pratt](algorithms/strings/knuth_morris_pratt.py) + - [panagram](algorithms/strings/panagram.py) - [tree](algorithms/tree) - [bst](algorithms/tree/bst) - [array_to_bst](algorithms/tree/bst/array_to_bst.py) diff --git a/algorithms/strings/__init__.py b/algorithms/strings/__init__.py index 37c83dfdd..496b1ebe5 100644 --- a/algorithms/strings/__init__.py +++ b/algorithms/strings/__init__.py @@ -38,3 +38,4 @@ from .atbash_cipher import * from .longest_palindromic_substring import * from .knuth_morris_pratt import * +from .panagram import * \ No newline at end of file diff --git a/algorithms/strings/panagram.py b/algorithms/strings/panagram.py new file mode 100644 index 000000000..d34965356 --- /dev/null +++ b/algorithms/strings/panagram.py @@ -0,0 +1,36 @@ +""" +Given a string, check whether it is a panagram or not. + +A panagram is a sentence that uses every letter at least once. + +The most famous example is: "he quick brown fox jumps over the lazy dog. + +Note: +A panagram in one language isn't necessarily a panagram in another. This +module assumes the english language. Hence, the Finnish panagram +'Törkylempijävongahdus' won't pass for a panagram despite being considered +a perfect panagram in its language. However, the Swedish panagram +'Yxmördaren Julia Blomqvist på fäktning i Schweiz' will pass despite +including letters not used in the english alphabet. This is because the +Swedish alphabet only extends the Latin one. +""" + +from string import ascii_lowercase + +def panagram(string): + """ + Returns whether the input string is an English panagram or not. + + Parameters: + string (str): A sentence in the form of a string. + + Returns: + A boolean with the result. + """ + letters = set(ascii_lowercase) + for c in string: + try: + letters.remove(c.lower()) + except: + pass + return len(letters) == 0 \ No newline at end of file diff --git a/tests/test_strings.py b/tests/test_strings.py index 5544086fa..519f12370 100644 --- a/tests/test_strings.py +++ b/tests/test_strings.py @@ -40,7 +40,8 @@ repeat_substring, atbash, longest_palindrome, - knuth_morris_pratt + knuth_morris_pratt, + panagram ) import unittest @@ -572,6 +573,84 @@ def test_knuth_morris_pratt(self): self.assertEqual([0, 4], knuth_morris_pratt("abcdabc", "abc")) self.assertEqual([], knuth_morris_pratt("aabcdaab", "aba")) +class TestPanagram(unittest.TestCase): + """[summary] + Test for the file panagram.py + + Arguments: + unittest {[type]} -- [description] + """ + + def test_empty_string(self): + # Arrange + string = "" + + # Act + res = panagram(string) + + # Assert + self.assertEqual(False, res) + + def test_single_word_non_panagram(self): + # Arrange + string = "sentence" + + # Act + res = panagram(string) + + # Assert + self.assertEqual(False, res) + + def test_fox_panagram_no_spaces(self): + # Arrange + string = "thequickbrownfoxjumpsoverthelazydog" + + # Act + res = panagram(string) + + # Assert + self.assertEqual(True, res) + + def test_fox_panagram_mixed_case(self): + # Arrange + string = "theqUiCkbrOwnfOxjUMPSOVErThELAzYDog" + + # Act + res = panagram(string) + + # Assert + self.assertEqual(True, res) + + def test_whitespace_punctuation(self): + # Arrange + string = "\n\t\r,.-_!?" + + # Act + res = panagram(string) + + # Assert + self.assertEqual(False, res) + + def test_fox_panagram(self): + # Arrange + string = "the quick brown fox jumps over the lazy dog" + + # Act + res = panagram(string) + + # Assert + self.assertEqual(True, res) + + def test_swedish_panagram(self): + # Arrange + string = "Yxmördaren Julia Blomqvist på fäktning i Schweiz" + + # Act + res = panagram(string) + + # Assert + self.assertEqual(True, res) + if __name__ == "__main__": unittest.main() From 8f6e5f0aa54fe82c00d677cda2f8bef345f001ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A5ns=20Andersson?= <41705928+mansand1@users.noreply.github.com> Date: Fri, 12 Mar 2021 00:54:07 +0100 Subject: [PATCH 055/101] Add chinese remainder theorem (#759) * feat: Add basic ch. remainder theorem algorithm * feat: Add all n coprime check Co-authored-by: Lazar Cerovic * Add gcd function * Add list length > 0 check * doc: Improve function documentation * feat: add all divisors need to be > 1 * test: Add test cases for crt solver * fix: make check_coprime private * fix: Change to python3.7 type hints * refactor: Move ch. remainder theorem tests to test_maths * Add link in README * Remove unnecessary whitespace and add newline at end of file * docs: Fix README alphabetic order --- README.md | 1 + algorithms/maths/chinese_remainder_theorem.py | 46 +++++++++++++++++++ tests/test_maths.py | 33 ++++++++++++- 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 algorithms/maths/chinese_remainder_theorem.py diff --git a/README.md b/README.md index 948fe6c4c..8ccf3ba4c 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,7 @@ If you want to uninstall algorithms, it is as simple as: - [is_anagram](algorithms/map/is_anagram.py) - [maths](algorithms/maths) - [base_conversion](algorithms/maths/base_conversion.py) + - [chinese_remainder_theorem](algorithms/maths/chinese_remainder_theorem.py) - [combination](algorithms/maths/combination.py) - [cosine_similarity](algorithms/maths/cosine_similarity.py) - [decimal_to_binary_ip](algorithms/maths/decimal_to_binary_ip.py) diff --git a/algorithms/maths/chinese_remainder_theorem.py b/algorithms/maths/chinese_remainder_theorem.py new file mode 100644 index 000000000..91d9d1d9a --- /dev/null +++ b/algorithms/maths/chinese_remainder_theorem.py @@ -0,0 +1,46 @@ +from algorithms.maths.gcd import gcd +from typing import List + +def solve_chinese_remainder(num : List[int], rem : List[int]): + """ + Computes the smallest x that satisfies the chinese remainder theorem + for a system of equations. + The system of equations has the form: + x % num[0] = rem[0] + x % num[1] = rem[1] + ... + x % num[k - 1] = rem[k - 1] + Where k is the number of elements in num and rem, k > 0. + All numbers in num needs to be pariwise coprime otherwise an exception is raised + returns x: the smallest value for x that satisfies the system of equations + """ + if not len(num) == len(rem): + raise Exception("num and rem should have equal length") + if not len(num) > 0: + raise Exception("Lists num and rem need to contain at least one element") + for n in num: + if not n > 1: + raise Exception("All numbers in num needs to be > 1") + if not _check_coprime(num): + raise Exception("All pairs of numbers in num are not coprime") + k = len(num) + x = 1 + while True: + i = 0 + while i < k: + if x % num[i] != rem[i]: + break + i += 1 + if i == k: + return x + else: + x += 1 + +def _check_coprime(l : List[int]): + for i in range(len(l)): + for j in range(len(l)): + if i == j: + continue + if gcd(l[i], l[j]) != 1: + return False + return True diff --git a/tests/test_maths.py b/tests/test_maths.py index 95db71088..967cf64d9 100644 --- a/tests/test_maths.py +++ b/tests/test_maths.py @@ -25,7 +25,8 @@ alice_private_key, alice_public_key, bob_private_key, bob_public_key, alice_shared_key, bob_shared_key, diffie_hellman_key_exchange, num_digits, alice_private_key, alice_public_key, bob_private_key, bob_public_key, alice_shared_key, bob_shared_key, - diffie_hellman_key_exchange, krishnamurthy_number + diffie_hellman_key_exchange, krishnamurthy_number, + chinese_remainder_theorem, ) import unittest @@ -496,6 +497,36 @@ def test_num_digits(self): self.assertEqual(1,num_digits(-5)) self.assertEqual(3,num_digits(-254)) +class TestChineseRemainderSolver(unittest.TestCase): + def test_k_three(self): + # Example which should give the answer 143 + # which is the smallest possible x that + # solves the system of equations + num = [3, 7, 10] + rem = [2, 3, 3] + self.assertEqual(chinese_remainder_theorem.solve_chinese_remainder(num, rem), 143) + + def test_k_five(self): + # Example which should give the answer 3383 + # which is the smallest possible x that + # solves the system of equations + num = [3, 5, 7, 11, 26] + rem = [2, 3, 2, 6, 3] + self.assertEqual(chinese_remainder_theorem.solve_chinese_remainder(num, rem), 3383) + + def test_exception_non_coprime(self): + # There should be an exception when all + # numbers in num are not pairwise coprime + num = [3, 7, 10, 14] + rem = [2, 3, 3, 1] + with self.assertRaises(Exception): + chinese_remainder_theorem.solve_chinese_remainder(num, rem) + + def test_empty_lists(self): + num = [] + rem = [] + with self.assertRaises(Exception): + chinese_remainder_theorem.solve_chinese_remainder(num, rem) if __name__ == "__main__": unittest.main() From ab5de86b01fd7a8b166aadac92db056f8a048928 Mon Sep 17 00:00:00 2001 From: trangology Date: Tue, 23 Mar 2021 10:40:35 +0300 Subject: [PATCH 056/101] Fix failed test with negative search key & refactor code (#775) --- algorithms/search/interpolation_search.py | 30 ++++++++++++++--------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/algorithms/search/interpolation_search.py b/algorithms/search/interpolation_search.py index 0759180e3..9227e1f75 100644 --- a/algorithms/search/interpolation_search.py +++ b/algorithms/search/interpolation_search.py @@ -21,12 +21,14 @@ def interpolation_search(array: List[int], search_key: int) -> int: :returns: Index of search_key in array if found, else -1. - Example + Examples: - >>> interpolation_search([1, 10, 12, 15, 20, 41, 55], 20) - 4 + >>> interpolation_search([-25, -12, -1, 10, 12, 15, 20, 41, 55], -1) + 2 >>> interpolation_search([5, 10, 12, 14, 17, 20, 21], 55) -1 + >>> interpolation_search([5, 10, 12, 14, 17, 20, 21], -5) + -1 """ @@ -34,20 +36,26 @@ def interpolation_search(array: List[int], search_key: int) -> int: high = len(array) - 1 low = 0 - while low <= high and search_key in range(low, array[high] + 1): + while (low <= high) and (array[low] <= search_key <= array[high]): # calculate the search position pos = low + int(((search_key - array[low]) * (high - low) / (array[high] - array[low]))) - # if array[pos] equals the search_key then return pos as the index - if search_key == array[pos]: + # search_key is found + if array[pos] == search_key: return pos - # if the search_key is greater than array[pos] restart the search with the - # subarray greater than array[pos] - elif search_key > array[pos]: + + # if search_key is larger, search_key is in upper part + if array[pos] < search_key: low = pos + 1 - # in this case start the search with the subarray smaller than current array[pos] - elif search_key < array[pos]: + + # if search_key is smaller, search_key is in lower part + else: high = pos - 1 return -1 + + +if __name__ == "__main__": + import doctest + doctest.testmod() From 5995c053b41d0aeb922db908053fc6a1604d13cc Mon Sep 17 00:00:00 2001 From: Caroline Borg Date: Tue, 23 Mar 2021 11:26:24 +0100 Subject: [PATCH 057/101] Fix #768 (#770) --- README.md | 1 + algorithms/dp/__init__.py | 1 + algorithms/dp/planting_trees.py | 45 +++++++++++++++++++++++++++++++++ tests/test_dp.py | 27 +++++++++++++++++++- 4 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 algorithms/dp/planting_trees.py diff --git a/README.md b/README.md index 8ccf3ba4c..fdb9c0b7f 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,7 @@ If you want to uninstall algorithms, it is as simple as: - [fibonacci](algorithms/dp/fib.py) - [hosoya triangle](algorithms/dp/hosoya_triangle.py) - [K-Factor_strings](algorithms/dp/k_factor.py) + - [planting_trees](algorithms/dp/planting_trees.py) - [graph](algorithms/graph) - [check_bipartite](algorithms/graph/check_bipartite.py) - [strongly_connected](algorithms/graph/check_digraph_strongly_connected.py) diff --git a/algorithms/dp/__init__.py b/algorithms/dp/__init__.py index 4492a9c9b..ac56eda74 100644 --- a/algorithms/dp/__init__.py +++ b/algorithms/dp/__init__.py @@ -20,3 +20,4 @@ from .word_break import * from .int_divide import * from .k_factor import * +from .planting_trees import * \ No newline at end of file diff --git a/algorithms/dp/planting_trees.py b/algorithms/dp/planting_trees.py new file mode 100644 index 000000000..c1efd4e64 --- /dev/null +++ b/algorithms/dp/planting_trees.py @@ -0,0 +1,45 @@ +""" +An even number of trees are left along one side of a country road. You've been assigned the job to +plant these trees at an even interval on both sides of the road. The length L and width W of the road +are variable, and a pair of trees must be planted at the beginning (at 0) and at the end (at L) of +the road. Only one tree can be moved at a time. The goal is to calculate the lowest amount of +distance that the trees have to be moved before they are all in a valid position. +""" + +from math import sqrt +import sys + +def planting_trees(trees, L, W): + """ + Returns the minimum distance that trees have to be moved before they are all in a valid state. + + Parameters: + tree (list): A sorted list of integers with all trees' position along the road. + L (int): An integer with the length of the road. + W (int): An integer with the width of the road. + + Returns: + A float number with the total distance trees have been moved. + """ + trees = [0] + trees + + n_pairs = int(len(trees)/2) + + space_between_pairs = L/(n_pairs-1) + + target_locations = [location*space_between_pairs for location in range(n_pairs)] + + cmatrix = [[0 for _ in range(n_pairs+1)] for _ in range(n_pairs+1)] + for ri in range(1, n_pairs+1): + cmatrix[ri][0] = cmatrix[ri-1][0] + sqrt(W + abs(trees[ri]-target_locations[ri-1])**2) + for li in range(1, n_pairs+1): + cmatrix[0][li] = cmatrix[0][li-1] + abs(trees[li]-target_locations[li-1]) + + for ri in range(1, n_pairs+1): + for li in range(1, n_pairs+1): + cmatrix[ri][li] = min( + cmatrix[ri-1][li] + sqrt(W + (trees[li + ri]-target_locations[ri-1])**2), + cmatrix[ri][li-1] + abs(trees[li + ri]-target_locations[li-1]) + ) + + return cmatrix[n_pairs][n_pairs] \ No newline at end of file diff --git a/tests/test_dp.py b/tests/test_dp.py index 177c5659f..300be3d85 100644 --- a/tests/test_dp.py +++ b/tests/test_dp.py @@ -13,7 +13,8 @@ longest_increasing_subsequence, longest_increasing_subsequence_optimized, longest_increasing_subsequence_optimized2, - int_divide,find_k_factor + int_divide,find_k_factor, + planting_trees ) @@ -182,6 +183,30 @@ def test_kfactor(self): k5=1 self.assertEqual(find_k_factor(n5,k5),71284044) +class TestPlantingTrees(unittest.TestCase): + def test_simple(self): + # arrange + trees = [0, 1, 10, 10] + L = 10 + W = 1 + + # act + res = planting_trees(trees, L, W) + + # assert + self.assertEqual(res, 2.414213562373095) + + def test_simple2(self): + # arrange + trees = [0, 3, 5, 5, 6, 9] + L = 10 + W = 1 + + # act + res = planting_trees(trees, L, W) + + # assert + self.assertEqual(res, 9.28538328578604) if __name__ == '__main__': unittest.main() From 339a0d276a2b216242c2828674e58e6043a4e708 Mon Sep 17 00:00:00 2001 From: Trenton South Date: Wed, 12 May 2021 16:03:55 -0500 Subject: [PATCH 058/101] fix_rotate.py (#778) * fix_rotate.py The way it was, you couldn't pass in a rotation higher than the length of the string doubled without getting a blank string in return. This way allows you to pass in any positive integer and get a result. Also, The last line of the description said that it took 2 strings and returned a boolean. This is not correct. The first line of the comment is correct. * Update rotate.py After submitting my last change, I realized it would have been better to not keep concatenating the string. * Updated Comments rotate.py --- algorithms/strings/rotate.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/algorithms/strings/rotate.py b/algorithms/strings/rotate.py index 7f713baf5..8be2bd4ba 100644 --- a/algorithms/strings/rotate.py +++ b/algorithms/strings/rotate.py @@ -1,18 +1,19 @@ """ Given a strings s and int k, return a string that rotates k times +k can be any positive integer. + For example, rotate("hello", 2) return "llohe" rotate("hello", 5) return "hello" rotate("hello", 6) return "elloh" rotate("hello", 7) return "llohe" +rotate("hello", 102) return "lohel" -accepts two strings -returns bool """ def rotate(s, k): - double_s = s + s + long_string = s * (k // len(s) + 2) if k <= len(s): - return double_s[k:k + len(s)] + return long_string[k:k + len(s)] else: - return double_s[k-len(s):k] + return long_string[k-len(s):k] From 0d3e4c12ed8ddf0f5a2f447afea06bfd3fa125e9 Mon Sep 17 00:00:00 2001 From: Clifton McCook <56374673+cliftonamccook@users.noreply.github.com> Date: Sun, 30 May 2021 18:57:25 -0500 Subject: [PATCH 059/101] Corrected spelling anf grammar errors in docstring for heap. (#783) --- algorithms/heap/binary_heap.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/algorithms/heap/binary_heap.py b/algorithms/heap/binary_heap.py index 629be434a..927065b76 100644 --- a/algorithms/heap/binary_heap.py +++ b/algorithms/heap/binary_heap.py @@ -1,7 +1,7 @@ """ -Binary Heap. A min heap is a complete binary tree where each node is smaller -its childen. The root, therefore, is the minimum element in the tree. The min -heap use array to represent the data and operation. For example a min heap: +Binary Heap. A min heap is a complete binary tree where each node is smaller than +its children. The root, therefore, is the minimum element in the tree. The min +heap uses an array to represent the data and operation. For example a min heap: 4 / \ From 9b6b2523096dc2dfa8f6efe9016b1790ee4ecc47 Mon Sep 17 00:00:00 2001 From: Clifton McCook <56374673+cliftonamccook@users.noreply.github.com> Date: Sun, 30 May 2021 19:49:13 -0500 Subject: [PATCH 060/101] Updated binary_heap (#785) * Corrected spelling anf grammar errors in docstring for heap. * Fixed method signature for remove_min by removing unused parameter i. * Fixed grammar in min_child docstring. --- algorithms/heap/binary_heap.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/algorithms/heap/binary_heap.py b/algorithms/heap/binary_heap.py index 927065b76..ac7ff63bc 100644 --- a/algorithms/heap/binary_heap.py +++ b/algorithms/heap/binary_heap.py @@ -48,7 +48,7 @@ def perc_down(self,i): def min_child(self,i): pass @abstractmethod - def remove_min(self,i): + def remove_min(self): pass class BinaryHeap(AbstractHeap): def __init__(self): @@ -64,8 +64,8 @@ def perc_up(self, i): """ Method insert always start by inserting the element at the bottom. - it inserts rightmost spot so as to maintain the complete tree property - Then, it fix the tree by swapping the new element with its parent, + It inserts rightmost spot so as to maintain the complete tree property. + Then, it fixes the tree by swapping the new element with its parent, until it finds an appropriate spot for the element. It essentially perc_up the minimum element Complexity: O(logN) @@ -76,7 +76,7 @@ def insert(self, val): self.perc_up(self.currentSize) """ - Method min_child returns index of smaller 2 childs of its parent + Method min_child returns the index of smaller of 2 children of parent at index i """ def min_child(self, i): if 2 * i + 1 > self.currentSize: # No right child @@ -104,7 +104,7 @@ def perc_down(self, i): """ def remove_min(self): ret = self.heap[1] # the smallest value at beginning - self.heap[1] = self.heap[self.currentSize] # Repalce it by the last value + self.heap[1] = self.heap[self.currentSize] # Replace it by the last value self.currentSize = self.currentSize - 1 self.heap.pop() self.perc_down(1) From 5dde6a0f18af460642a363f57cfd431c1c4c8f66 Mon Sep 17 00:00:00 2001 From: Clifton McCook <56374673+cliftonamccook@users.noreply.github.com> Date: Sun, 30 May 2021 21:24:09 -0500 Subject: [PATCH 061/101] Fixed some invalid references and more spelling errors. (#786) * Corrected spelling anf grammar errors in docstring for heap. * Fixed method signature for remove_min by removing unused parameter i. * Fixed grammar in min_child docstring. * Fixed spelling error. * Fixed spelling errors in matrix_exponentiation.py * Removed trailing semicolons in matrix_inversion.py. TODO: Check for possible type error in line 61. * Fixed spelling error in base_conversion.py. * Fixed incorrect reference in min_height.py * Fixed incorrect reference in max_height.py --- algorithms/linkedlist/linkedlist.py | 2 +- algorithms/maths/base_conversion.py | 2 +- algorithms/matrix/matrix_exponentiation.py | 4 ++-- algorithms/matrix/matrix_inversion.py | 8 ++++---- algorithms/tree/max_height.py | 2 +- algorithms/tree/min_height.py | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/algorithms/linkedlist/linkedlist.py b/algorithms/linkedlist/linkedlist.py index 1b921c926..c7b8e0c4a 100644 --- a/algorithms/linkedlist/linkedlist.py +++ b/algorithms/linkedlist/linkedlist.py @@ -3,7 +3,7 @@ # in comparison, arrays require O(n) time to do the same thing. # Linked lists can continue to expand without having to specify # their size ahead of time (remember our lectures on Array sizing -# form the Array Sequence section of the course!) +# from the Array Sequence section of the course!) # Cons # To access an element in a linked list, you need to take O(k) time diff --git a/algorithms/maths/base_conversion.py b/algorithms/maths/base_conversion.py index cc3b62cbb..5d2a2214e 100644 --- a/algorithms/maths/base_conversion.py +++ b/algorithms/maths/base_conversion.py @@ -33,7 +33,7 @@ def int_to_base(n, base): def base_to_int(s, base): """ - Note : You can use int() built-in function instread of this. + Note : You can use int() built-in function instead of this. :type s: str :type base: int :rtype: int diff --git a/algorithms/matrix/matrix_exponentiation.py b/algorithms/matrix/matrix_exponentiation.py index f842bc2fd..f9673ce4a 100644 --- a/algorithms/matrix/matrix_exponentiation.py +++ b/algorithms/matrix/matrix_exponentiation.py @@ -16,7 +16,7 @@ def multiply(matA: list, matB: list) -> list: def identity(n: int) -> list: """ Returns the Identity matrix of size n x n - Time Complecity: O(n^2) + Time Complexity: O(n^2) """ I = [[0 for i in range(n)] for j in range(n)] @@ -29,7 +29,7 @@ def matrix_exponentiation(mat: list, n: int) -> list: """ Calculates mat^n by repeated squaring Time Complexity: O(d^3 log(n)) - d: dimesion of the square matrix mat + d: dimension of the square matrix mat n: power the matrix is raised to """ if n == 0: diff --git a/algorithms/matrix/matrix_inversion.py b/algorithms/matrix/matrix_inversion.py index 7dfb093ff..8d8102d41 100644 --- a/algorithms/matrix/matrix_inversion.py +++ b/algorithms/matrix/matrix_inversion.py @@ -28,16 +28,16 @@ def invert_matrix(m): # Error conditions if not array_is_matrix(m): print("Invalid matrix: array is not a matrix") - return [[-1]]; + return [[-1]] elif len(m) != len(m[0]): print("Invalid matrix: matrix is not square") - return [[-2]]; + return [[-2]] elif len(m) < 2: print("Invalid matrix: matrix is too small") - return [[-3]]; + return [[-3]] elif get_determinant(m) == 0: print("Invalid matrix: matrix is square, but singular (determinant = 0)") - return [[-4]]; + return [[-4]] # Calculation elif len(m) == 2: diff --git a/algorithms/tree/max_height.py b/algorithms/tree/max_height.py index ee9c80cde..c732ac079 100644 --- a/algorithms/tree/max_height.py +++ b/algorithms/tree/max_height.py @@ -12,7 +12,7 @@ # iterative -from tree.tree import TreeNode +from tree import TreeNode def max_height(root): diff --git a/algorithms/tree/min_height.py b/algorithms/tree/min_height.py index a33517dec..7913e2965 100644 --- a/algorithms/tree/min_height.py +++ b/algorithms/tree/min_height.py @@ -1,4 +1,4 @@ -from tree.tree import TreeNode +from tree import TreeNode def min_depth(self, root): From 779bc2fb4515cdff8e927cdbadeb2fdff7f426bf Mon Sep 17 00:00:00 2001 From: Karan Gadani <52932210+karangadani@users.noreply.github.com> Date: Mon, 28 Jun 2021 21:08:04 +0530 Subject: [PATCH 062/101] LPS Algorithm is Added Fixed Issues #746 (#788) * Longest Palindromic Subsequence Algorithm is Added * test case added * Renamed --- README.md | 1 + .../map/longest_palindromic_subsequence.py | 22 +++++++++++++++++++ tests/test_map.py | 15 ++++++++++++- 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 algorithms/map/longest_palindromic_subsequence.py diff --git a/README.md b/README.md index fdb9c0b7f..105c82505 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,7 @@ If you want to uninstall algorithms, it is as simple as: - [hashtable](algorithms/map/hashtable.py) - [separate_chaining_hashtable](algorithms/map/separate_chaining_hashtable.py) - [longest_common_subsequence](algorithms/map/longest_common_subsequence.py) + - [longest_palindromic_subsequence](algorithms/map/longest_palindromic_subsequence.py) - [randomized_set](algorithms/map/randomized_set.py) - [valid_sudoku](algorithms/map/valid_sudoku.py) - [word_pattern](algorithms/map/word_pattern.py) diff --git a/algorithms/map/longest_palindromic_subsequence.py b/algorithms/map/longest_palindromic_subsequence.py new file mode 100644 index 000000000..93cc6dd8b --- /dev/null +++ b/algorithms/map/longest_palindromic_subsequence.py @@ -0,0 +1,22 @@ +def longest_palindromic_subsequence(str): + n = len(str) + + # Create a table to store results of subproblems + L = [[0 for x in range(n)] for x in range(n)] + + for i in range(n): + L[i][i] = 1 + + + for sub_string_length in range(2, n + 1): + for i in range(n-sub_string_length + 1): + j = i + sub_string_length-1 + if str[i] == str[j] and sub_string_length == 2: + L[i][j] = 2 + elif str[i] == str[j]: + L[i][j] = L[i + 1][j-1] + 2 + else: + L[i][j] = max(L[i][j-1], L[i + 1][j]); + + return L[0][n-1] + diff --git a/tests/test_map.py b/tests/test_map.py index aaff34a98..2df7ca0fa 100644 --- a/tests/test_map.py +++ b/tests/test_map.py @@ -3,7 +3,8 @@ Node, SeparateChainingHashTable, word_pattern, is_isomorphic, - is_anagram + is_anagram, + longest_palindromic_subsequence, ) import unittest @@ -164,6 +165,18 @@ def test_is_isomorphic(self): self.assertFalse(is_isomorphic("foo", "bar")) self.assertTrue(is_isomorphic("paper", "title")) +class TestLongestPalindromicSubsequence(unittest.TestCase): + def test_longest_palindromic_subsequence_is_correct(self): + self.assertEqual(7,longest_palindromic_subsequence('BBABCBCAB')) + self.assertEqual(4,longest_palindromic_subsequence('abbaeae')) + self.assertEqual(8,longest_palindromic_subsequence('babbbababaa')) + self.assertEqual(6,longest_palindromic_subsequence('daccandeeja')) + + def test_longest_palindromic_subsequence_is_incorrect(self): + self.assertEqual(4,longest_palindromic_subsequence('BBABCBCAB')) + self.assertEqual(5,longest_palindromic_subsequence('abbaeae')) + self.assertEqual(2,longest_palindromic_subsequence('babbbababaa')) + self.assertEqual(1,longest_palindromic_subsequence('daccandeeja')) class TestIsAnagram(unittest.TestCase): def test_is_anagram(self): From d54f4a5628c2ccf8b7b358017c74377defd2773e Mon Sep 17 00:00:00 2001 From: kq58 <1772617802@qq.com> Date: Tue, 10 Aug 2021 12:30:14 +0800 Subject: [PATCH 063/101] this could be an implementation of lps. (#796) * Update longest_palindromic_subsequence.py * Update __init__.py * Update test_map.py --- algorithms/map/__init__.py | 1 + .../map/longest_palindromic_subsequence.py | 49 +++++++++++-------- tests/test_map.py | 14 +++--- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/algorithms/map/__init__.py b/algorithms/map/__init__.py index 233348d37..931c0e776 100644 --- a/algorithms/map/__init__.py +++ b/algorithms/map/__init__.py @@ -3,3 +3,4 @@ from .word_pattern import * from .is_isomorphic import * from .is_anagram import * +from .longest_palindromic_subsequence import * diff --git a/algorithms/map/longest_palindromic_subsequence.py b/algorithms/map/longest_palindromic_subsequence.py index 93cc6dd8b..767e78f44 100644 --- a/algorithms/map/longest_palindromic_subsequence.py +++ b/algorithms/map/longest_palindromic_subsequence.py @@ -1,22 +1,29 @@ -def longest_palindromic_subsequence(str): - n = len(str) - - # Create a table to store results of subproblems - L = [[0 for x in range(n)] for x in range(n)] - - for i in range(n): - L[i][i] = 1 - - - for sub_string_length in range(2, n + 1): - for i in range(n-sub_string_length + 1): - j = i + sub_string_length-1 - if str[i] == str[j] and sub_string_length == 2: - L[i][j] = 2 - elif str[i] == str[j]: - L[i][j] = L[i + 1][j-1] + 2 - else: - L[i][j] = max(L[i][j-1], L[i + 1][j]); - - return L[0][n-1] +def longest_palindromic_subsequence(s): + + k = len(s) + olist = [0] * k # 申请长度为n的列表,并初始化 + nList = [0] * k # 同上 + logestSubStr = "" + logestLen = 0 + for j in range(0, k): + for i in range(0, j + 1): + if j - i <= 1: + if s[i] == s[j]: + nList[i] = 1 # 当 j 时,第 i 个子串为回文子串 + len_t = j - i + 1 + if logestLen < len_t: # 判断长度 + logestSubStr = s[i:j + 1] + logestLen = len_t + else: + if s[i] == s[j] and olist[i+1]: # 当j-i>1时,判断s[i]是否等于s[j],并判断当j-1时,第i+1个子串是否为回文子串 + nList[i] = 1 # 当 j 时,第 i 个子串为回文子串 + len_t = j - i + 1 + if logestLen < len_t: + logestSubStr = s[i:j + 1] + logestLen = len_t + olist = nList # 覆盖旧的列表 + nList = [0] * k # 新的列表清空 + # ~ from icecream import ic + # ~ ic(s, logestSubStr) + return logestLen#, logestSubStr diff --git a/tests/test_map.py b/tests/test_map.py index 2df7ca0fa..c8164881b 100644 --- a/tests/test_map.py +++ b/tests/test_map.py @@ -167,16 +167,16 @@ def test_is_isomorphic(self): class TestLongestPalindromicSubsequence(unittest.TestCase): def test_longest_palindromic_subsequence_is_correct(self): - self.assertEqual(7,longest_palindromic_subsequence('BBABCBCAB')) + self.assertEqual(3,longest_palindromic_subsequence('BBABCBCAB')) self.assertEqual(4,longest_palindromic_subsequence('abbaeae')) - self.assertEqual(8,longest_palindromic_subsequence('babbbababaa')) - self.assertEqual(6,longest_palindromic_subsequence('daccandeeja')) + self.assertEqual(7,longest_palindromic_subsequence('babbbababaa')) + self.assertEqual(4,longest_palindromic_subsequence('daccandeeja')) def test_longest_palindromic_subsequence_is_incorrect(self): - self.assertEqual(4,longest_palindromic_subsequence('BBABCBCAB')) - self.assertEqual(5,longest_palindromic_subsequence('abbaeae')) - self.assertEqual(2,longest_palindromic_subsequence('babbbababaa')) - self.assertEqual(1,longest_palindromic_subsequence('daccandeeja')) + self.assertNotEqual(4,longest_palindromic_subsequence('BBABCBCAB')) + self.assertNotEqual(5,longest_palindromic_subsequence('abbaeae')) + self.assertNotEqual(2,longest_palindromic_subsequence('babbbababaa')) + self.assertNotEqual(1,longest_palindromic_subsequence('daccandeeja')) class TestIsAnagram(unittest.TestCase): def test_is_anagram(self): From 6e7e1f26350c2538a0ed6384e6a3332797fe9bc0 Mon Sep 17 00:00:00 2001 From: Banuteja <67595337+PAPPALA-BANUTEJA-NAIDU@users.noreply.github.com> Date: Thu, 2 Sep 2021 07:35:17 +0530 Subject: [PATCH 064/101] Add another implementation (#802) * Add tests for rotate.py * Add another implementation --- algorithms/strings/rotate.py | 4 ++++ tests/test_strings.py | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/algorithms/strings/rotate.py b/algorithms/strings/rotate.py index 8be2bd4ba..5bfcd1971 100644 --- a/algorithms/strings/rotate.py +++ b/algorithms/strings/rotate.py @@ -17,3 +17,7 @@ def rotate(s, k): return long_string[k:k + len(s)] else: return long_string[k-len(s):k] + +def rotate_alt(string, k): + k = k % len(string) + return string[k:] + string[:k] diff --git a/tests/test_strings.py b/tests/test_strings.py index 519f12370..1158630b1 100644 --- a/tests/test_strings.py +++ b/tests/test_strings.py @@ -225,6 +225,12 @@ def test_rotate(self): self.assertEqual("hello", rotate("hello", 5)) self.assertEqual("elloh", rotate("hello", 6)) self.assertEqual("llohe", rotate("hello", 7)) + + def test_rotate_alt(self): + self.assertEqual("llohe", rotate_alt("hello", 2)) + self.assertEqual("hello", rotate_alt("hello", 5)) + self.assertEqual("elloh", rotate_alt("hello", 6)) + self.assertEqual("llohe", rotate_alt("hello", 7)) class TestLicenseNumber(unittest.TestCase): From a9e57d459557f0bcd2bad1e8fac302ab72d34fe8 Mon Sep 17 00:00:00 2001 From: Banuteja <67595337+PAPPALA-BANUTEJA-NAIDU@users.noreply.github.com> Date: Fri, 3 Sep 2021 05:31:28 +0530 Subject: [PATCH 065/101] Palindrome using deque (#803) * Add method using deque * updated tests for is_palindrome.py deque --- algorithms/strings/is_palindrome.py | 18 ++++++++++++++++++ tests/test_strings.py | 7 ++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/algorithms/strings/is_palindrome.py b/algorithms/strings/is_palindrome.py index 40f404a37..11a9e3063 100644 --- a/algorithms/strings/is_palindrome.py +++ b/algorithms/strings/is_palindrome.py @@ -11,6 +11,7 @@ we define empty string as valid palindrome. """ from string import ascii_letters +from collections import deque def is_palindrome(s): @@ -85,3 +86,20 @@ def is_palindrome_stack(s): if s[i] != stack.pop(): return False return True + +# Variation 4 (using deque) +def is_palindrome_deque(s): + s = remove_punctuation(s) + deq = deque() + for char in s: + deq.appendleft(char) + + equal = True + + while len(deq) > 1 and equal: + first = deq.pop() + last = deq.popleft() + if first != last : + equal = False + + return equal diff --git a/tests/test_strings.py b/tests/test_strings.py index 1158630b1..2b1110866 100644 --- a/tests/test_strings.py +++ b/tests/test_strings.py @@ -8,7 +8,7 @@ group_anagrams, int_to_roman, is_palindrome, is_palindrome_reverse, - is_palindrome_two_pointer, is_palindrome_stack, + is_palindrome_two_pointer, is_palindrome_stack, is_palindrome_deque, is_rotated, is_rotated_v1, license_number, make_sentence, @@ -195,6 +195,11 @@ def test_is_palindrome_stack(self): self.assertTrue(is_palindrome_stack("Otto")) self.assertFalse(is_palindrome_stack("house")) + def test_is_palindrome_deque(self): + # 'Otto' is a old german name. + self.assertTrue(is_palindrome_deque("Otto")) + self.assertFalse(is_palindrome_deque("house")) + class TestIsRotated(unittest.TestCase): """[summary] From 34871c5be9a109c3b614832b75041a0ed707c53c Mon Sep 17 00:00:00 2001 From: Mo Yi Date: Mon, 8 Nov 2021 23:45:43 +0800 Subject: [PATCH 066/101] typo (#811) --- algorithms/tree/segment_tree/segment_tree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/algorithms/tree/segment_tree/segment_tree.py b/algorithms/tree/segment_tree/segment_tree.py index 2bf51966f..94aeba5dd 100644 --- a/algorithms/tree/segment_tree/segment_tree.py +++ b/algorithms/tree/segment_tree/segment_tree.py @@ -8,7 +8,7 @@ def __init__(self,arr,function): self.segment = [0 for x in range(3*len(arr)+3)] self.arr = arr self.fn = function - self.maketree(0,0,len(arr)-1) + self.make_tree(0,0,len(arr)-1) def make_tree(self,i,l,r): if l==r: From 1dcf31c4f9845fd3c2b5b385ff5c6682220376ee Mon Sep 17 00:00:00 2001 From: Nate Wilkinson Date: Wed, 17 Nov 2021 08:54:46 -0700 Subject: [PATCH 067/101] Bugfix: Add missing import for rotate_alt test Adds a missing import for the test of rotate_alt --- tests/test_strings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_strings.py b/tests/test_strings.py index 2b1110866..907a3897f 100644 --- a/tests/test_strings.py +++ b/tests/test_strings.py @@ -35,7 +35,7 @@ text_justification, min_distance, longest_common_prefix_v1, longest_common_prefix_v2, longest_common_prefix_v3, - rotate, + rotate, rotate_alt, first_unique_char, repeat_substring, atbash, From 02d451da3d3e51e017cfff47c4f28f5b757ba02c Mon Sep 17 00:00:00 2001 From: Milad Khoshdel Date: Thu, 18 Nov 2021 11:49:17 +0330 Subject: [PATCH 068/101] Increase performance of Function 3 Times Faster Response --- algorithms/arrays/limit.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/algorithms/arrays/limit.py b/algorithms/arrays/limit.py index c6100da1c..e0cc30586 100644 --- a/algorithms/arrays/limit.py +++ b/algorithms/arrays/limit.py @@ -14,7 +14,4 @@ # tl:dr -- array slicing by value def limit(arr, min_lim=None, max_lim=None): - min_check = lambda val: True if min_lim is None else (min_lim <= val) - max_check = lambda val: True if max_lim is None else (val <= max_lim) - - return [val for val in arr if min_check(val) and max_check(val)] + return list(filter(lambda x: (min_lim <= x <= max_lim), arr)) From 23d4e85a506eaeaff315e855be12f8dbe47a7ec3 Mon Sep 17 00:00:00 2001 From: KimAYoung <57066971+Kim-AYoung@users.noreply.github.com> Date: Thu, 16 Dec 2021 22:02:43 +0900 Subject: [PATCH 069/101] Added exchange sort (#816) * add_exchange_sort --- README.md | 1 + algorithms/sort/__init__.py | 1 + algorithms/sort/exchange_sort.py | 11 +++++++++++ tests/test_sort.py | 4 ++++ 4 files changed, 17 insertions(+) create mode 100644 algorithms/sort/exchange_sort.py diff --git a/README.md b/README.md index 105c82505..d8fc9c46f 100644 --- a/README.md +++ b/README.md @@ -283,6 +283,7 @@ If you want to uninstall algorithms, it is as simple as: - [comb_sort](algorithms/sort/comb_sort.py) - [counting_sort](algorithms/sort/counting_sort.py) - [cycle_sort](algorithms/sort/cycle_sort.py) + - [exchange_sort](algorithms/sort/exchange_sort.py) - [gnome_sort](algorithms/sort/gnome_sort.py) - [heap_sort](algorithms/sort/heap_sort.py) - [insertion_sort](algorithms/sort/insertion_sort.py) diff --git a/algorithms/sort/__init__.py b/algorithms/sort/__init__.py index b948dba48..fb186c024 100644 --- a/algorithms/sort/__init__.py +++ b/algorithms/sort/__init__.py @@ -4,6 +4,7 @@ from .comb_sort import * from .counting_sort import * from .cycle_sort import * +from .exchange_sort import * from .heap_sort import * from .insertion_sort import * from .merge_sort import * diff --git a/algorithms/sort/exchange_sort.py b/algorithms/sort/exchange_sort.py new file mode 100644 index 000000000..c2d2e7923 --- /dev/null +++ b/algorithms/sort/exchange_sort.py @@ -0,0 +1,11 @@ +def exchange_sort(arr): + """ + Reference : https://en.wikipedia.org/wiki/Sorting_algorithm#Exchange_sort + Complexity : O(n^2) + """ + arr_len = len(arr) + for i in range(arr_len-1): + for j in range(i+1, arr_len): + if(arr[i] > arr[j]): + arr[i], arr[j] = arr[j], arr[i] + return arr diff --git a/tests/test_sort.py b/tests/test_sort.py index e3fb498b7..2c23ab8a5 100644 --- a/tests/test_sort.py +++ b/tests/test_sort.py @@ -5,6 +5,7 @@ comb_sort, counting_sort, cycle_sort, + exchange_sort, max_heap_sort, min_heap_sort, insertion_sort, merge_sort, @@ -55,6 +56,9 @@ def test_counting_sort(self): def test_cycle_sort(self): self.assertTrue(is_sorted(cycle_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) + def test_exchange_sort(self): + self.assertTrue(is_sorted(exchange_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) + def test_heap_sort(self): self.assertTrue(is_sorted(max_heap_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) From 65cc8551d86d7e065069d165dd8bf9baf10345a0 Mon Sep 17 00:00:00 2001 From: Mithun Date: Sun, 9 Jan 2022 11:19:33 +0400 Subject: [PATCH 070/101] minor updates (#822) --- algorithms/stack/stack.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/algorithms/stack/stack.py b/algorithms/stack/stack.py index 216c26492..d1a148e36 100644 --- a/algorithms/stack/stack.py +++ b/algorithms/stack/stack.py @@ -8,10 +8,8 @@ It needs no parameters and returns the item. The stack is modified. peek() returns the top item from the stack but does not remove it. It needs no parameters. The stack is not modified. -isEmpty() tests to see whether the stack is empty. +is_empty() tests to see whether the stack is empty. It needs no parameters and returns a boolean value. -size() returns the number of items on the stack. - It needs no parameters and returns an integer. """ from abc import ABCMeta, abstractmethod class AbstractStack(metaclass=ABCMeta): @@ -72,7 +70,7 @@ def push(self, value): def pop(self): if self.is_empty(): - raise IndexError("stack is empty") + raise IndexError("Stack is empty") value = self._array[self._top] self._top -= 1 return value @@ -80,7 +78,7 @@ def pop(self): def peek(self): """returns the current top element of the stack.""" if self.is_empty(): - raise IndexError("stack is empty") + raise IndexError("Stack is empty") return self._array[self._top] def _expand(self): From 6fb00bc3a9aa9ba99ffb2ab16835c5f1bb67a438 Mon Sep 17 00:00:00 2001 From: Ankit Agarwal Date: Tue, 15 Feb 2022 10:05:17 +0530 Subject: [PATCH 071/101] Create python-app.yml --- .github/workflows/python-app.yml | 36 ++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/workflows/python-app.yml diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml new file mode 100644 index 000000000..8c262bdd2 --- /dev/null +++ b/.github/workflows/python-app.yml @@ -0,0 +1,36 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Python application + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.10 + uses: actions/setup-python@v2 + with: + python-version: "3.10" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,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 + - name: Test with pytest + run: | + pytest From c12305ca4ba67d06b6448fb9be17f592a93e56e3 Mon Sep 17 00:00:00 2001 From: Aalekh Patel Date: Tue, 15 Feb 2022 23:52:29 -0600 Subject: [PATCH 072/101] Update pytest command. (#831) Invoke pytest as a python module rather than a standalone executable. --- .github/workflows/python-app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 8c262bdd2..1eb737ecd 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -33,4 +33,4 @@ jobs: flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest run: | - pytest + python -m pytest From a3109408486ad22ddb563e6cd2f5b0921233543c Mon Sep 17 00:00:00 2001 From: Aalekh Patel Date: Wed, 16 Feb 2022 00:00:43 -0600 Subject: [PATCH 073/101] Bug fix: Add None checks for the boundary values. (#832) Its bugging me that CI workflow is failing because of a test for this function. This bug fix should bring green ticks back again. ;) --- algorithms/arrays/limit.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/algorithms/arrays/limit.py b/algorithms/arrays/limit.py index e0cc30586..57f6561f8 100644 --- a/algorithms/arrays/limit.py +++ b/algorithms/arrays/limit.py @@ -14,4 +14,12 @@ # tl:dr -- array slicing by value def limit(arr, min_lim=None, max_lim=None): + if len(arr) == 0: + return arr + + if min_lim is None: + min_lim = min(arr) + if max_lim is None: + max_lim = max(arr) + return list(filter(lambda x: (min_lim <= x <= max_lim), arr)) From 6c54611213d0b6a0615f503dceaa962302f5f071 Mon Sep 17 00:00:00 2001 From: Per Arn Date: Thu, 24 Feb 2022 11:13:32 +0100 Subject: [PATCH 074/101] Added testing for the fizzbuzz algorithm. (#833) --- tests/test_strings.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/tests/test_strings.py b/tests/test_strings.py index 907a3897f..893498156 100644 --- a/tests/test_strings.py +++ b/tests/test_strings.py @@ -41,7 +41,8 @@ atbash, longest_palindrome, knuth_morris_pratt, - panagram + panagram, + fizzbuzz ) import unittest @@ -662,6 +663,33 @@ def test_swedish_panagram(self): # Assert self.assertEqual(True, res) - +class TestFizzbuzz(unittest.TestCase): + """[summary] + Tests for the fizzbuzz method in file fizzbuzz.py + """ + + def test_fizzbuzz(self): + # Testing that n < 0 returns a Value Error + self.assertRaises(ValueError, fizzbuzz.fizzbuzz, -2) + + # Testing that a string returns a Type Error. + self.assertRaises(TypeError, fizzbuzz.fizzbuzz, "hello") + + # Testing a base case, n = 3 + result = fizzbuzz.fizzbuzz(3) + expected = [1, 2, "Fizz"] + self.assertEqual(result, expected) + + # Testing a base case, n = 5 + result = fizzbuzz.fizzbuzz(5) + expected = [1, 2, "Fizz", 4, "Buzz"] + self.assertEqual(result, expected) + + # Testing a base case, n = 15 i.e. mod 3 and 5 + result = fizzbuzz.fizzbuzz(15) + expected = [1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz", 11, + "Fizz", 13, 14, "FizzBuzz"] + self.assertEqual(result, expected) + if __name__ == "__main__": unittest.main() From 617d2d5ad654b451aafadf90304358a0358c309e Mon Sep 17 00:00:00 2001 From: James Wiens Date: Thu, 24 Feb 2022 23:18:58 -0800 Subject: [PATCH 075/101] Fix broken link in wiki (fenwick tree) (#790) --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index d8fc9c46f..2234b67fa 100644 --- a/README.md +++ b/README.md @@ -370,7 +370,6 @@ If you want to uninstall algorithms, it is as simple as: - [count_left_node](algorithms/tree/bst/count_left_node.py) - [num_empty](algorithms/tree/bst/num_empty.py) - [height](algorithms/tree/bst/height.py) - - [fenwick_tree](algorithms/tree/fenwick_tree] - [fenwick_tree](algorithms/tree/fenwick_tree/fenwick_tree.py) - [red_black_tree](algorithms/tree/red_black_tree) - [red_black_tree](algorithms/tree/red_black_tree/red_black_tree.py) From 87eae9a25751a13d4ef48860fffa4df0998356eb Mon Sep 17 00:00:00 2001 From: Ankit Agarwal Date: Wed, 2 Mar 2022 09:28:54 +0530 Subject: [PATCH 076/101] Fix flake8 issues (#836) --- algorithms/tree/is_balanced.py | 5 +- docs/source/conf.py | 14 +-- setup.py | 2 - tests/test_array.py | 90 ++++++++++------ tests/test_automata.py | 22 ++-- tests/test_backtrack.py | 106 +++++++++++-------- tests/test_bfs.py | 27 +++-- tests/test_bit.py | 28 +++-- tests/test_compression.py | 25 +++-- tests/test_dfs.py | 58 +++++++---- tests/test_dp.py | 62 +++++------ tests/test_graph.py | 149 +++++++++++++------------- tests/test_heap.py | 6 +- tests/test_iterative_segment_tree.py | 24 +++-- tests/test_map.py | 29 +++--- tests/test_maths.py | 44 ++++---- tests/test_matrix.py | 55 ++++++---- tests/test_ml.py | 33 +++--- tests/test_monomial.py | 68 +++++++----- tests/test_queues.py | 9 +- tests/test_search.py | 3 +- tests/test_set.py | 4 +- tests/test_sort.py | 50 +++++---- tests/test_stack.py | 3 + tests/test_streaming.py | 37 ++++--- tests/test_strings.py | 150 ++++++++++++++++----------- tests/test_tree.py | 16 ++- tests/test_unix.py | 14 ++- 28 files changed, 665 insertions(+), 468 deletions(-) diff --git a/algorithms/tree/is_balanced.py b/algorithms/tree/is_balanced.py index 004daaa37..3910e1b7e 100644 --- a/algorithms/tree/is_balanced.py +++ b/algorithms/tree/is_balanced.py @@ -15,7 +15,7 @@ def __get_depth(root): """ if root is None: return 0 - left = __get_depth(root.left) + left = __get_depth(root.left) right = __get_depth(root.right) if abs(left-right) > 1 or -1 in [left, right]: return -1 @@ -28,7 +28,8 @@ def __get_depth(root): # """ # left = max_height(root.left) # right = max_height(root.right) -# return abs(left-right) <= 1 and is_balanced(root.left) and is_balanced(root.right) +# return abs(left-right) <= 1 and is_balanced(root.left) and +# is_balanced(root.right) # def max_height(root): # if root is None: diff --git a/docs/source/conf.py b/docs/source/conf.py index b6c0c8320..768277d1b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -32,13 +32,13 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ['sphinx.ext.autodoc', - 'sphinx.ext.doctest', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.mathjax', - 'sphinx.ext.ifconfig', - 'sphinx.ext.viewcode', - 'sphinx.ext.githubpages'] + 'sphinx.ext.doctest', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.mathjax', + 'sphinx.ext.ifconfig', + 'sphinx.ext.viewcode', + 'sphinx.ext.githubpages'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/setup.py b/setup.py index 56099a55c..c5bb20141 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,4 @@ -import os import io -import re from setuptools import find_packages, setup diff --git a/tests/test_array.py b/tests/test_array.py index c02b2754e..83a4840d5 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -3,7 +3,8 @@ flatten_iter, flatten, garage, josephus, - longest_non_repeat_v1, longest_non_repeat_v2, get_longest_non_repeat_v1, get_longest_non_repeat_v2, + longest_non_repeat_v1, longest_non_repeat_v2, + get_longest_non_repeat_v1, get_longest_non_repeat_v2, Interval, merge_intervals, missing_ranges, move_zeros, @@ -51,12 +52,14 @@ def test_delete_nth_naive(self): [1, 1, 3, 3, 7, 2, 2, 2, 2], n=3), [1, 1, 3, 3, 7, 2, 2, 2]) self.assertListEqual(delete_nth_naive( - [1, 2, 3, 1, 1, 2, 1, 2, 3, 3, 2, 4, 5, 3, 1], n=3), + [1, 2, 3, 1, 1, 2, 1, 2, 3, 3, 2, 4, 5, 3, 1], + n=3), [1, 2, 3, 1, 1, 2, 2, 3, 3, 4, 5]) self.assertListEqual(delete_nth_naive([], n=5), []) self.assertListEqual(delete_nth_naive( - [1, 2, 3, 1, 1, 2, 1, 2, 3, 3, 2, 4, 5, 3, 1], n=0), + [1, 2, 3, 1, 1, 2, 1, 2, 3, 3, 2, 4, 5, 3, 1], + n=0), []) def test_delete_nth(self): @@ -65,11 +68,13 @@ def test_delete_nth(self): [20, 37, 21]) self.assertListEqual(delete_nth([1, 1, 3, 3, 7, 2, 2, 2, 2], n=3), [1, 1, 3, 3, 7, 2, 2, 2]) - self.assertListEqual(delete_nth([1, 2, 3, 1, 1, 2, 1, 2, 3, 3, 2, 4, 5, 3, 1], n=3), + self.assertListEqual(delete_nth([1, 2, 3, 1, 1, 2, 1, 2, 3, 3, 2, 4, + 5, 3, 1], n=3), [1, 2, 3, 1, 1, 2, 2, 3, 3, 4, 5]) self.assertListEqual(delete_nth([], n=5), []) - self.assertListEqual(delete_nth([1, 2, 3, 1, 1, 2, 1, 2, 3, 3, 2, 4, 5, 3, 1], n=0), + self.assertListEqual(delete_nth([1, 2, 3, 1, 1, 2, 1, 2, 3, 3, 2, 4, + 5, 3, 1], n=0), []) @@ -169,9 +174,8 @@ def test_longest_non_repeat_v2(self): string = "asjrgapa" self.assertEqual(longest_non_repeat_v2(string), 6) - + def test_get_longest_non_repeat_v1(self): - string = "abcabcbb" self.assertEqual(get_longest_non_repeat_v1(string), (3, 'abc')) @@ -188,7 +192,6 @@ def test_get_longest_non_repeat_v1(self): self.assertEqual(get_longest_non_repeat_v1(string), (6, 'sjrgap')) def test_get_longest_non_repeat_v2(self): - string = "abcabcbb" self.assertEqual(get_longest_non_repeat_v2(string), (3, 'abc')) @@ -209,9 +212,12 @@ class TestMaxOnesIndex(unittest.TestCase): def test_max_ones_index(self): - self.assertEqual(9, max_ones_index([1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1])) - self.assertEqual(3, max_ones_index([1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1])) - self.assertEqual(-1, max_ones_index([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])) + self.assertEqual(9, max_ones_index([1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, + 1, 1])) + self.assertEqual(3, max_ones_index([1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, + 1, 1])) + self.assertEqual(-1, max_ones_index([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1])) class TestMergeInterval(unittest.TestCase): @@ -255,7 +261,8 @@ def test_move_zeros(self): self.assertListEqual(move_zeros([False, 1, 0, 1, 2, 0, 1, 3, "a"]), [False, 1, 1, 2, 1, 3, "a", 0, 0]) - self.assertListEqual(move_zeros([0, 34, 'rahul', [], None, 0, True, 0]), + self.assertListEqual(move_zeros([0, 34, 'rahul', [], None, 0, + True, 0]), [34, 'rahul', [], None, True, 0, 0, 0]) @@ -297,31 +304,31 @@ class TestRotateArray(unittest.TestCase): def test_rotate_v1(self): self.assertListEqual(rotate_v1([1, 2, 3, 4, 5, 6, 7], k=3), - [5, 6, 7, 1, 2, 3, 4]) + [5, 6, 7, 1, 2, 3, 4]) self.assertListEqual(rotate_v1([1, 2, 3, 4, 5, 6, 7], k=1), - [7, 1, 2, 3, 4, 5, 6]) + [7, 1, 2, 3, 4, 5, 6]) self.assertListEqual(rotate_v1([1, 2, 3, 4, 5, 6, 7], k=7), - [1, 2, 3, 4, 5, 6, 7]) + [1, 2, 3, 4, 5, 6, 7]) self.assertListEqual(rotate_v1([1, 2], k=111), [2, 1]) def test_rotate_v2(self): self.assertListEqual(rotate_v2([1, 2, 3, 4, 5, 6, 7], k=3), - [5, 6, 7, 1, 2, 3, 4]) + [5, 6, 7, 1, 2, 3, 4]) self.assertListEqual(rotate_v2([1, 2, 3, 4, 5, 6, 7], k=1), - [7, 1, 2, 3, 4, 5, 6]) + [7, 1, 2, 3, 4, 5, 6]) self.assertListEqual(rotate_v2([1, 2, 3, 4, 5, 6, 7], k=7), - [1, 2, 3, 4, 5, 6, 7]) + [1, 2, 3, 4, 5, 6, 7]) self.assertListEqual(rotate_v2([1, 2], k=111), [2, 1]) def test_rotate_v3(self): self.assertListEqual(rotate_v3([1, 2, 3, 4, 5, 6, 7], k=3), - [5, 6, 7, 1, 2, 3, 4]) + [5, 6, 7, 1, 2, 3, 4]) self.assertListEqual(rotate_v3([1, 2, 3, 4, 5, 6, 7], k=1), - [7, 1, 2, 3, 4, 5, 6]) + [7, 1, 2, 3, 4, 5, 6]) self.assertListEqual(rotate_v3([1, 2, 3, 4, 5, 6, 7], k=7), - [1, 2, 3, 4, 5, 6, 7]) + [1, 2, 3, 4, 5, 6, 7]) self.assertListEqual(rotate_v3([1, 2], k=111), [2, 1]) @@ -363,14 +370,16 @@ class TestTrimmean(unittest.TestCase): def test_trimmean(self): self.assertEqual(trimmean([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 20), 5.5) - self.assertEqual(trimmean([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 20), 6.0) + self.assertEqual(trimmean([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 20), + 6.0) class TestTop1(unittest.TestCase): def test_top_1(self): - self.assertListEqual(top_1([1 , 1, 2, 2, 3]), [1, 2]) - self.assertListEqual(top_1([1, 2, 3, 324, 234, 23, 23, 1, 23, 23]), [23]) + self.assertListEqual(top_1([1, 1, 2, 2, 3]), [1, 2]) + self.assertListEqual(top_1([1, 2, 3, 324, 234, 23, 23, 1, 23, 23]), + [23]) class TestLimit(unittest.TestCase): @@ -385,21 +394,34 @@ class TestNSum(unittest.TestCase): def test_n_sum(self): self.assertEqual(n_sum(2, [-3, 5, 2, 3, 8, -9], 6), []) # noqa: E501 - self.assertEqual(n_sum(3, [-5, -4, -3, -2, -1, 0, 1, 2, 3], 0), sorted([[-5,2,3],[-2,0,2],[-4,1,3],[-3,1,2],[-1,0,1],[-2,-1,3],[-3,0,3]])) # noqa: E501 - self.assertEqual(n_sum(3, [-1,0,1,2,-1,-4], 0), sorted([[-1,-1,2],[-1,0,1]])) # noqa: E501 - self.assertEqual(n_sum(4, [1, 0, -1, 0, -2, 2], 0), sorted([[-2, -1, 1, 2], [-2, 0, 0, 2], [-1, 0, 0, 1]])) # noqa: E501 + self.assertEqual(n_sum(3, [-5, -4, -3, -2, -1, 0, 1, 2, 3], 0), + sorted([[-5, 2, 3], [-2, 0, 2], [-4, 1, 3], + [-3, 1, 2], [-1, 0, 1], [-2, -1, 3], + [-3, 0, 3]])) # noqa: E501 + self.assertEqual(n_sum(3, [-1, 0, 1, 2, -1, -4], 0), + sorted([[-1, -1, 2], [-1, 0, 1]])) # noqa: E501 + self.assertEqual(n_sum(4, [1, 0, -1, 0, -2, 2], 0), + sorted([[-2, -1, 1, 2], [-2, 0, 0, 2], + [-1, 0, 0, 1]])) # noqa: E501 self.assertEqual(n_sum(4, [7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 6, 4, -3, -2], 10), sorted([[-6, 2, 7, 7], [-6, 3, 6, 7], [-6, 4, 5, 7], [-6, 4, 6, 6], [-5, 1, 7, 7], [-5, 2, 6, 7], [-5, 3, 5, 7], [-5, 3, 6, 6], [-5, 4, 4, 7], [-5, 4, 5, 6], [-4, 0, 7, 7], [-4, 1, 6, 7], [-4, 2, 5, 7], [-4, 2, 6, 6], [-4, 3, 4, 7], [-4, 3, 5, 6], [-4, 4, 4, 6], [-3, -1, 7, 7], [-3, 0, 6, 7], [-3, 1, 5, 7], [-3, 1, 6, 6], [-3, 2, 4, 7], [-3, 2, 5, 6], [-3, 3, 4, 6], [-3, 4, 4, 5], [-2, -2, 7, 7], [-2, -1, 6, 7], [-2, 0, 5, 7], [-2, 0, 6, 6], [-2, 1, 4, 7], [-2, 1, 5, 6], [-2, 2, 3, 7], [-2, 2, 4, 6], [-2, 3, 4, 5], [-1, 0, 4, 7], [-1, 0, 5, 6], [-1, 1, 3, 7], [-1, 1, 4, 6], [-1, 2, 3, 6], [-1, 2, 4, 5], [-1, 3, 4, 4], [0, 1, 2, 7], [0, 1, 3, 6], [0, 1, 4, 5], [0, 2, 3, 5], [0, 2, 4, 4], [1, 2, 3, 4]])) # noqa: E501 - self.assertEqual(n_sum(2, [[-3, 0], [-2, 1], [2, 2], [3, 3], [8, 4], [-9, 5]], 0, # noqa: E501 + self.assertEqual(n_sum(2, [[-3, 0], [-2, 1], [2, 2], [3, 3], [8, 4], + [-9, 5]], 0, # noqa: E501 sum_closure=lambda a, b: a[0] + b[0]), # noqa: E501 [[[-3, 0], [3, 3]], [[-2, 1], [2, 2]]]) # noqa: E501 - self.assertEqual(n_sum(2, [[-3, 0], [-2, 1], [2, 2], [3, 3], [8, 4], [-9, 5]], [0, 3], # noqa: E501 - sum_closure=lambda a, b: [a[0] + b[0], a[1] + b[1]], # noqa: E501 - same_closure=lambda a, b: a[0] == b[0] and a[1] == b[1]), # noqa: E501 + self.assertEqual(n_sum(2, [[-3, 0], [-2, 1], [2, 2], [3, 3], [8, 4], + [-9, 5]], [0, 3], # noqa: E501 + sum_closure=lambda a, b: [a[0] + b[0], + a[1] + b[1]], # noqa: E501 + same_closure=lambda a, b: a[0] == b[0] + and a[1] == b[1]), # noqa: E501 [[[-3, 0], [3, 3]], [[-2, 1], [2, 2]]]) # noqa: E501 - self.assertEqual(n_sum(2, [[-3, 0], [-2, 1], [2, 2], [3, 3], [8, 4], [-9, 5]], -5, # noqa: E501 - sum_closure=lambda a, b: [a[0] + b[1], a[1] + b[0]], # noqa: E501 - compare_closure=lambda a, b: -1 if a[0] < b else 1 if a[0] > b else 0), # noqa: E501 + self.assertEqual(n_sum(2, [[-3, 0], [-2, 1], [2, 2], [3, 3], + [8, 4], [-9, 5]], -5, # noqa: E501 + sum_closure=lambda a, b: [a[0] + b[1], + a[1] + b[0]], # noqa: E501 + compare_closure=lambda a, b: -1 if a[0] < b + else 1 if a[0] > b else 0), # noqa: E501 [[[-9, 5], [8, 4]]]) # noqa: E501 diff --git a/tests/test_automata.py b/tests/test_automata.py index 201c3d41b..dbd766a3f 100644 --- a/tests/test_automata.py +++ b/tests/test_automata.py @@ -6,43 +6,43 @@ class TestDFA(unittest.TestCase): def test_DFA(self): - transitions = { 'a': {'1': 'a', '0': 'b'}, 'b': {'1': 'b', '0': 'a'} } - final=['a'] + final = ['a'] start = 'a' - + self.assertEqual(False, DFA(transitions, start, final, "000111100")) self.assertEqual(True, DFA(transitions, start, final, "111000011")) - + transitions1 = { '0': {'0': '1', '1': '0'}, '1': {'0': '2', '1': '0'}, '2': {'0': '2', '1': '3'}, '3': {'0': '3', '1': '3'} - } - + } + final1 = ['0', '1', '2'] start1 = '0' - + self.assertEqual(False, DFA(transitions1, start1, final1, "0001111")) self.assertEqual(True, DFA(transitions1, start1, final1, "01010101")) - + transitions2 = { '0': {'a': '0', 'b': '1'}, '1': {'a': '0', 'b': '2'}, '2': {'a': '3', 'b': '2'}, '3': {'a': '3', 'b': '3'} } - - final2=['3'] + + final2 = ['3'] start2 = '0' self.assertEqual(False, DFA(transitions2, start2, final2, "aaabbb")) self.assertEqual(True, DFA(transitions2, start2, final2, "baabba")) - + + if __name__ == '__main__': unittest.main() diff --git a/tests/test_backtrack.py b/tests/test_backtrack.py index 682a89b04..4e9b9f612 100644 --- a/tests/test_backtrack.py +++ b/tests/test_backtrack.py @@ -1,6 +1,5 @@ from algorithms.backtrack import ( add_operators, - permute, permute_iter, anagram, array_sum_combinations, @@ -82,7 +81,8 @@ def test_array_sum_combinations(self): [1, 3, 3], [1, 4, 2], [2, 2, 3], [2, 2, 3], [2, 3, 2], [2, 3, 2], [3, 2, 2], [3, 2, 2]] answer.sort() - self.assertListEqual(sorted(array_sum_combinations(A, B, C, target)), answer) + self.assertListEqual(sorted(array_sum_combinations(A, B, C, target)), + answer) def test_unique_array_sum_combinations(self): A = [1, 2, 3, 3] @@ -92,7 +92,9 @@ def test_unique_array_sum_combinations(self): answer = [(2, 3, 2), (3, 2, 2), (1, 2, 4), (1, 4, 2), (2, 2, 3), (1, 3, 3)] answer.sort() - self.assertListEqual(sorted(unique_array_sum_combinations(A, B, C, target)), answer) + self.assertListEqual(sorted(unique_array_sum_combinations(A, B, C, + target)), + answer) class TestCombinationSum(unittest.TestCase): @@ -135,7 +137,7 @@ def test_get_factors(self): [4, 8] ] self.assertEqual(sorted(get_factors(target1)), sorted(answer1)) - + target2 = 12 answer2 = [ [2, 6], @@ -145,7 +147,7 @@ def test_get_factors(self): self.assertEqual(sorted(get_factors(target2)), sorted(answer2)) self.assertEqual(sorted(get_factors(1)), []) self.assertEqual(sorted(get_factors(37)), []) - + def test_recursive_get_factors(self): target1 = 32 answer1 = [ @@ -156,15 +158,17 @@ def test_recursive_get_factors(self): [2, 4, 4], [4, 8] ] - self.assertEqual(sorted(recursive_get_factors(target1)), sorted(answer1)) - + self.assertEqual(sorted(recursive_get_factors(target1)), + sorted(answer1)) + target2 = 12 answer2 = [ [2, 6], [2, 2, 3], [3, 4] ] - self.assertEqual(sorted(recursive_get_factors(target2)), sorted(answer2)) + self.assertEqual(sorted(recursive_get_factors(target2)), + sorted(answer2)) self.assertEqual(sorted(recursive_get_factors(1)), []) self.assertEqual(sorted(recursive_get_factors(37)), []) @@ -221,15 +225,18 @@ class TestGenerateAbbreviations(unittest.TestCase): def test_generate_abbreviations(self): word1 = "word" answer1 = ['word', 'wor1', 'wo1d', 'wo2', 'w1rd', 'w1r1', 'w2d', 'w3', - '1ord', '1or1', '1o1d', '1o2', '2rd', '2r1', '3d', '4'] - self.assertEqual(sorted(generate_abbreviations(word1)), sorted(answer1)) + '1ord', '1or1', '1o1d', '1o2', '2rd', '2r1', '3d', '4'] + self.assertEqual(sorted(generate_abbreviations(word1)), + sorted(answer1)) word2 = "hello" answer2 = ['hello', 'hell1', 'hel1o', 'hel2', 'he1lo', 'he1l1', 'he2o', - 'he3', 'h1llo', 'h1ll1', 'h1l1o', 'h1l2', 'h2lo', 'h2l1', 'h3o', 'h4', - '1ello', '1ell1', '1el1o', '1el2', '1e1lo', '1e1l1', '1e2o', '1e3', - '2llo', '2ll1', '2l1o', '2l2', '3lo', '3l1', '4o', '5'] - self.assertEqual(sorted(generate_abbreviations(word2)), sorted(answer2)) + 'he3', 'h1llo', 'h1ll1', 'h1l1o', 'h1l2', 'h2lo', 'h2l1', + 'h3o', 'h4', '1ello', '1ell1', '1el1o', '1el2', '1e1lo', + '1e1l1', '1e2o', '1e3', '2llo', '2ll1', '2l1o', '2l2', + '3lo', '3l1', '4o', '5'] + self.assertEqual(sorted(generate_abbreviations(word2)), + sorted(answer2)) class TestPatternMatch(unittest.TestCase): @@ -251,9 +258,11 @@ class TestGenerateParenthesis(unittest.TestCase): def test_generate_parenthesis(self): self.assertEqual(generate_parenthesis_v1(2), ['()()', '(())']) - self.assertEqual(generate_parenthesis_v1(3), ['()()()', '()(())', '(())()', '(()())', '((()))']) + self.assertEqual(generate_parenthesis_v1(3), ['()()()', '()(())', + '(())()', '(()())', '((()))']) self.assertEqual(generate_parenthesis_v2(2), ['(())', '()()']) - self.assertEqual(generate_parenthesis_v2(3), ['((()))', '(()())', '(())()', '()(())', '()()()']) + self.assertEqual(generate_parenthesis_v2(3), ['((()))', '(()())', + '(())()', '()(())', '()()()']) class TestLetterCombinations(unittest.TestCase): @@ -277,13 +286,17 @@ def test_palindromic_substrings(self): string2 = "abcba" answer2 = [['abcba'], ['a', 'bcb', 'a'], ['a', 'b', 'c', 'b', 'a']] - self.assertEqual(sorted(palindromic_substrings(string2)), sorted(answer2)) + self.assertEqual(sorted(palindromic_substrings(string2)), + sorted(answer2)) string3 = "abcccba" - answer3 = [['abcccba'], ['a', 'bcccb', 'a'], ['a', 'b', 'ccc', 'b', 'a'], - ['a', 'b', 'cc', 'c', 'b', 'a'], ['a', 'b', 'c', 'cc', 'b', 'a'], - ['a', 'b', 'c', 'c', 'c', 'b', 'a']] - self.assertEqual(sorted(palindromic_substrings(string3)), sorted(answer3)) + answer3 = [['abcccba'], ['a', 'bcccb', 'a'], + ['a', 'b', 'ccc', 'b', 'a'], + ['a', 'b', 'cc', 'c', 'b', 'a'], + ['a', 'b', 'c', 'cc', 'b', 'a'], + ['a', 'b', 'c', 'c', 'c', 'b', 'a']] + self.assertEqual(sorted(palindromic_substrings(string3)), + sorted(answer3)) class TestPermuteUnique(unittest.TestCase): @@ -294,12 +307,14 @@ def test_permute_unique(self): self.assertEqual(sorted(permute_unique(nums1)), sorted(answer1)) nums2 = [1, 2, 1, 3] - answer2 = [[3, 1, 2, 1], [1, 3, 2, 1], [1, 2, 3, 1], [1, 2, 1, 3], [3, 2, 1, 1], - [2, 3, 1, 1], [2, 1, 3, 1], [2, 1, 1, 3], [3, 1, 1, 2], [1, 3, 1, 2], [1, 1, 3, 2], [1, 1, 2, 3]] + answer2 = [[3, 1, 2, 1], [1, 3, 2, 1], [1, 2, 3, 1], [1, 2, 1, 3], + [3, 2, 1, 1], [2, 3, 1, 1], [2, 1, 3, 1], [2, 1, 1, 3], + [3, 1, 1, 2], [1, 3, 1, 2], [1, 1, 3, 2], [1, 1, 2, 3]] self.assertEqual(sorted(permute_unique(nums2)), sorted(answer2)) nums3 = [1, 2, 3] - answer3 = [[3, 2, 1], [2, 3, 1], [2, 1, 3], [3, 1, 2], [1, 3, 2], [1, 2, 3]] + answer3 = [[3, 2, 1], [2, 3, 1], [2, 1, 3], [3, 1, 2], + [1, 3, 2], [1, 2, 3]] self.assertEqual(sorted(permute_unique(nums3)), sorted(answer3)) @@ -307,28 +322,32 @@ class TestPermute(unittest.TestCase): def test_permute(self): nums1 = [1, 2, 3, 4] - answer1 = [[1, 2, 3, 4], [2, 1, 3, 4], [2, 3, 1, 4], [2, 3, 4, 1], [1, 3, 2, 4], - [3, 1, 2, 4], [3, 2, 1, 4], [3, 2, 4, 1], [1, 3, 4, 2], [3, 1, 4, 2], - [3, 4, 1, 2], [3, 4, 2, 1], [1, 2, 4, 3], [2, 1, 4, 3], [2, 4, 1, 3], - [2, 4, 3, 1], [1, 4, 2, 3], [4, 1, 2, 3], [4, 2, 1, 3], [4, 2, 3, 1], - [1, 4, 3, 2], [4, 1, 3, 2], [4, 3, 1, 2], [4, 3, 2, 1]] + answer1 = [[1, 2, 3, 4], [2, 1, 3, 4], [2, 3, 1, 4], [2, 3, 4, 1], + [1, 3, 2, 4], [3, 1, 2, 4], [3, 2, 1, 4], [3, 2, 4, 1], + [1, 3, 4, 2], [3, 1, 4, 2], [3, 4, 1, 2], [3, 4, 2, 1], + [1, 2, 4, 3], [2, 1, 4, 3], [2, 4, 1, 3], [2, 4, 3, 1], + [1, 4, 2, 3], [4, 1, 2, 3], [4, 2, 1, 3], [4, 2, 3, 1], + [1, 4, 3, 2], [4, 1, 3, 2], [4, 3, 1, 2], [4, 3, 2, 1]] self.assertEqual(sorted(permute(nums1)), sorted(answer1)) nums2 = [1, 2, 3] - answer2 = [[3, 2, 1], [2, 3, 1], [2, 1, 3], [3, 1, 2], [1, 3, 2], [1, 2, 3]] + answer2 = [[3, 2, 1], [2, 3, 1], [2, 1, 3], [3, 1, 2], + [1, 3, 2], [1, 2, 3]] self.assertEqual(sorted(permute(nums2)), sorted(answer2)) def test_permute_recursive(self): nums1 = [1, 2, 3, 4] - answer1 = [[1, 2, 3, 4], [2, 1, 3, 4], [2, 3, 1, 4], [2, 3, 4, 1], [1, 3, 2, 4], - [3, 1, 2, 4], [3, 2, 1, 4], [3, 2, 4, 1], [1, 3, 4, 2], [3, 1, 4, 2], - [3, 4, 1, 2], [3, 4, 2, 1], [1, 2, 4, 3], [2, 1, 4, 3], [2, 4, 1, 3], - [2, 4, 3, 1], [1, 4, 2, 3], [4, 1, 2, 3], [4, 2, 1, 3], [4, 2, 3, 1], - [1, 4, 3, 2], [4, 1, 3, 2], [4, 3, 1, 2], [4, 3, 2, 1]] + answer1 = [[1, 2, 3, 4], [2, 1, 3, 4], [2, 3, 1, 4], [2, 3, 4, 1], + [1, 3, 2, 4], [3, 1, 2, 4], [3, 2, 1, 4], [3, 2, 4, 1], + [1, 3, 4, 2], [3, 1, 4, 2], [3, 4, 1, 2], [3, 4, 2, 1], + [1, 2, 4, 3], [2, 1, 4, 3], [2, 4, 1, 3], [2, 4, 3, 1], + [1, 4, 2, 3], [4, 1, 2, 3], [4, 2, 1, 3], [4, 2, 3, 1], + [1, 4, 3, 2], [4, 1, 3, 2], [4, 3, 1, 2], [4, 3, 2, 1]] self.assertEqual(sorted(permute_recursive(nums1)), sorted(answer1)) nums2 = [1, 2, 3] - answer2 = [[3, 2, 1], [2, 3, 1], [2, 1, 3], [3, 1, 2], [1, 3, 2], [1, 2, 3]] + answer2 = [[3, 2, 1], [2, 3, 1], [2, 1, 3], [3, 1, 2], + [1, 3, 2], [1, 2, 3]] self.assertEqual(sorted(permute_recursive(nums2)), sorted(answer2)) @@ -340,8 +359,9 @@ def test_subsets_unique(self): self.assertEqual(sorted(subsets_unique(nums1)), sorted(answer1)) nums2 = [1, 2, 3, 4] - answer2 = [(1, 2), (1, 3), (1, 2, 3, 4), (1,), (2,), (3,), (1, 4), (1, 2, 3), - (4,), (), (2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4), (3, 4), (2, 4)] + answer2 = [(1, 2), (1, 3), (1, 2, 3, 4), (1,), (2,), (3,), + (1, 4), (1, 2, 3), (4,), (), (2, 3), (1, 2, 4), + (1, 3, 4), (2, 3, 4), (3, 4), (2, 4)] self.assertEqual(sorted(subsets_unique(nums2)), sorted(answer2)) @@ -353,8 +373,9 @@ def test_subsets(self): self.assertEqual(sorted(subsets(nums1)), sorted(answer1)) nums2 = [1, 2, 3, 4] - answer2 = [[1, 2, 3, 4], [1, 2, 3], [1, 2, 4], [1, 2], [1, 3, 4], - [1, 3], [1, 4], [1], [2, 3, 4], [2, 3], [2, 4], [2], [3, 4], [3], [4], []] + answer2 = [[1, 2, 3, 4], [1, 2, 3], [1, 2, 4], [1, 2], [1, 3, 4], + [1, 3], [1, 4], [1], [2, 3, 4], [2, 3], [2, 4], [2], + [3, 4], [3], [4], []] self.assertEqual(sorted(subsets(nums2)), sorted(answer2)) def test_subsets_v2(self): @@ -363,8 +384,9 @@ def test_subsets_v2(self): self.assertEqual(sorted(subsets_v2(nums1)), sorted(answer1)) nums2 = [1, 2, 3, 4] - answer2 = [[1, 2, 3, 4], [1, 2, 3], [1, 2, 4], [1, 2], [1, 3, 4], - [1, 3], [1, 4], [1], [2, 3, 4], [2, 3], [2, 4], [2], [3, 4], [3], [4], []] + answer2 = [[1, 2, 3, 4], [1, 2, 3], [1, 2, 4], [1, 2], [1, 3, 4], + [1, 3], [1, 4], [1], [2, 3, 4], [2, 3], [2, 4], [2], + [3, 4], [3], [4], []] self.assertEqual(sorted(subsets_v2(nums2)), sorted(answer2)) diff --git a/tests/test_bfs.py b/tests/test_bfs.py index 98bcb7eb4..f9b22f134 100644 --- a/tests/test_bfs.py +++ b/tests/test_bfs.py @@ -1,7 +1,6 @@ from algorithms.bfs import ( count_islands, maze_search, - shortest_distance_from_all_buildings, ladder_length ) @@ -11,22 +10,27 @@ class TestCountIslands(unittest.TestCase): def test_count_islands(self): - grid_1 = [[1,1,1,1,0], [1,1,0,1,0], [1,1,0,0,0], [0,0,0,0,0]] + grid_1 = [[1, 1, 1, 1, 0], [1, 1, 0, 1, 0], [1, 1, 0, 0, 0], + [0, 0, 0, 0, 0]] self.assertEqual(1, count_islands(grid_1)) - grid_2 = [[1,1,0,0,0], [1,1,0,0,0], [0,0,1,0,0], [0,0,0,1,1]] + grid_2 = [[1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [0, 0, 1, 0, 0], + [0, 0, 0, 1, 1]] self.assertEqual(3, count_islands(grid_2)) - grid_3 = [[1,1,1,0,0,0], [1,1,0,0,0,0], [1,0,0,0,0,1], [0,0,1,1,0,1], [0,0,1,1,0,0]] + grid_3 = [[1, 1, 1, 0, 0, 0], [1, 1, 0, 0, 0, 0], [1, 0, 0, 0, 0, 1], + [0, 0, 1, 1, 0, 1], [0, 0, 1, 1, 0, 0]] self.assertEqual(3, count_islands(grid_3)) - grid_4 = [[1,1,0,0,1,1], [0,0,1,1,0,0], [0,0,0,0,0,1], [1,1,1,1,0,0]] + grid_4 = [[1, 1, 0, 0, 1, 1], [0, 0, 1, 1, 0, 0], [0, 0, 0, 0, 0, 1], + [1, 1, 1, 1, 0, 0]] self.assertEqual(5, count_islands(grid_4)) class TestMazeSearch(unittest.TestCase): def test_maze_search(self): - grid_1 = [[1,0,1,1,1,1],[1,0,1,0,1,0],[1,0,1,0,1,1],[1,1,1,0,1,1]] + grid_1 = [[1, 0, 1, 1, 1, 1], [1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 1, 1], + [1, 1, 1, 0, 1, 1]] self.assertEqual(14, maze_search(grid_1)) - grid_2 = [[1,0,0],[0,1,1],[0,1,1]] + grid_2 = [[1, 0, 0], [0, 1, 1], [0, 1, 1]] self.assertEqual(-1, maze_search(grid_2)) @@ -35,11 +39,13 @@ class TestWordLadder(unittest.TestCase): def test_ladder_length(self): # hit -> hot -> dot -> dog -> cog - self.assertEqual(5, ladder_length('hit', 'cog', ["hot", "dot", "dog", "lot", "log"])) + self.assertEqual(5, ladder_length('hit', 'cog', ["hot", "dot", "dog", + "lot", "log"])) # pick -> sick -> sink -> sank -> tank == 5 self.assertEqual(5, ladder_length('pick', 'tank', - ['tock', 'tick', 'sank', 'sink', 'sick'])) + ['tock', 'tick', 'sank', 'sink', + 'sick'])) # live -> life == 1, no matter what is the word_list. self.assertEqual(1, ladder_length('live', 'life', ['hoho', 'luck'])) @@ -48,7 +54,8 @@ def test_ladder_length(self): self.assertEqual(0, ladder_length('ate', 'ate', [])) # not possible to reach ! - self.assertEqual(-1, ladder_length('rahul', 'coder', ['blahh', 'blhah'])) + self.assertEqual(-1, ladder_length('rahul', 'coder', ['blahh', + 'blhah'])) if __name__ == "__main__": diff --git a/tests/test_bit.py b/tests/test_bit.py index 23e9d94f5..c494a9762 100644 --- a/tests/test_bit.py +++ b/tests/test_bit.py @@ -11,7 +11,7 @@ single_number3, subsets, get_bit, set_bit, clear_bit, update_bit, - int_to_bytes_big_endian, int_to_bytes_little_endian, + int_to_bytes_big_endian, int_to_bytes_little_endian, bytes_big_endian_to_int, bytes_little_endian_to_int, swap_pair, find_difference, @@ -151,20 +151,24 @@ def test_single_number2(self): self.assertEqual(single, single_number2(nums)) def test_single_number3(self): - self.assertEqual(sorted([2,5]), + self.assertEqual(sorted([2, 5]), sorted(single_number3([2, 1, 5, 6, 6, 1]))) - self.assertEqual(sorted([4,3]), + self.assertEqual(sorted([4, 3]), sorted(single_number3([9, 9, 4, 3]))) def test_subsets(self): self.assertSetEqual(subsets([1, 2, 3]), - {(), (1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)}) + {(), (1,), (2,), (3,), (1, 2), (1, 3), (2, 3), + (1, 2, 3)}) self.assertSetEqual(subsets([10, 20, 30, 40]), - {(10, 40), (10, 20, 40), (10, 30), (10, 20, 30, 40), (40,), - (10, 30, 40), (30,), (20, 30), (30, 40), (10,), (), - (10, 20), (20, 40), (20, 30, 40), (10, 20, 30), (20,)}) + {(10, 40), (10, 20, 40), (10, 30), + (10, 20, 30, 40), (40,), + (10, 30, 40), (30,), (20, 30), (30, 40), (10,), + (), + (10, 20), (20, 40), (20, 30, 40), (10, 20, 30), + (20,)}) def test_get_bit(self): # 22 = 10110 @@ -180,20 +184,22 @@ def test_clear_bit(self): self.assertEqual(18, clear_bit(22, 2)) def test_update_bit(self): - # 22 = 10110 --> after update bit at 3th position with value 1: 30 = 11110 + # 22 = 10110 --> after update bit at 3th position with + # value 1: 30 = 11110 self.assertEqual(30, update_bit(22, 3, 1)) - # 22 = 10110 --> after update bit at 2nd position with value 0: 20 = 10010 + # 22 = 10110 --> after update bit at 2nd position with + # value 0: 20 = 10010 self.assertEqual(18, update_bit(22, 2, 0)) def test_int_to_bytes_big_endian(self): self.assertEqual(b'\x11', int_to_bytes_big_endian(17)) - + def test_int_to_bytes_little_endian(self): self.assertEqual(b'\x11', int_to_bytes_little_endian(17)) def test_bytes_big_endian_to_int(self): self.assertEqual(17, bytes_big_endian_to_int(b'\x11')) - + def test_bytes_little_endian_to_int(self): self.assertEqual(17, bytes_little_endian_to_int(b'\x11')) diff --git a/tests/test_compression.py b/tests/test_compression.py index cd412839d..503369c4a 100644 --- a/tests/test_compression.py +++ b/tests/test_compression.py @@ -35,35 +35,40 @@ def tearDown(self): os.remove(self.file_out_bin_name) os.remove(self.file_out_name) + class TestRLECompression(unittest.TestCase): - + def test_encode_rle(self): self.assertEqual('12W1B12W3B24W1B14W', - encode_rle('WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWBWWWWWWWWWWWWWW')) + encode_rle('WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWBWWWWWWWWWWWWWW')) def test_decode_rle(self): - self.assertEqual('WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWBWWWWWWWWWWWWWW', - decode_rle('12W1B12W3B24W1B14W')) + self.assertEqual('WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWBWWWWWWWWWWWWWW', + decode_rle('12W1B12W3B24W1B14W')) + class TestEliasCoding(unittest.TestCase): def test_elias_gamma(self): - correct_result = ['0', '00', '100', '101', '11000', '11001', '11010', '11011', '1110000', '1110001', '1110010'] - + correct_result = ['0', '00', '100', '101', '11000', '11001', '11010', + '11011', '1110000', '1110001', '1110010'] + result = [] - for i in range(11): + for i in range(11): result.append(elias_gamma(i)) self.assertEqual(correct_result, result) def test_elias_delta(self): - correct_result = ['0', '000', '1000', '1001', '10100', '10101', '10110', '10111', '11000000', '11000001', '11000010'] + correct_result = ['0', '000', '1000', '1001', '10100', '10101', + '10110', '10111', '11000000', '11000001', '11000010'] result = [] - for i in range(11): + for i in range(11): result.append(elias_delta(i)) - self.assertEqual(correct_result, result) + self.assertEqual(correct_result, result) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_dfs.py b/tests/test_dfs.py index 0f198e4a9..3164ab873 100644 --- a/tests/test_dfs.py +++ b/tests/test_dfs.py @@ -12,53 +12,69 @@ class TestAllFactors(unittest.TestCase): def test_get_factors(self): - self.assertEqual([[2, 16], [2, 2, 8], [2, 2, 2, 4], [2, 2, 2, 2, 2], [2, 4, 4], [4, 8]], - get_factors(32)) + self.assertEqual([[2, 16], [2, 2, 8], [2, 2, 2, 4], [2, 2, 2, 2, 2], + [2, 4, 4], [4, 8]], get_factors(32)) + def test_get_factors_iterative1(self): - self.assertEqual([[2, 16], [4, 8], [2, 2, 8], [2, 4, 4], [2, 2, 2, 4], [2, 2, 2, 2, 2]], - get_factors_iterative1(32)) + self.assertEqual([[2, 16], [4, 8], [2, 2, 8], [2, 4, 4], [2, 2, 2, 4], + [2, 2, 2, 2, 2]], get_factors_iterative1(32)) + def test_get_factors_iterative2(self): - self.assertEqual([[2, 2, 2, 2, 2], [2, 2, 2, 4], [2, 2, 8], [2, 4, 4], [2, 16], [4, 8]], - get_factors_iterative2(32)) + self.assertEqual([[2, 2, 2, 2, 2], [2, 2, 2, 4], [2, 2, 8], [2, 4, 4], + [2, 16], [4, 8]], get_factors_iterative2(32)) class TestCountIslands(unittest.TestCase): def test_num_islands(self): - self.assertEqual(1, num_islands([[1, 1, 1, 1, 0], [1, 1, 0, 1, 0], [1, 1, 0, 0, 0], [0, 0, 0, 0, 0]])) - self.assertEqual(3, num_islands([[1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, 1, 1]])) + self.assertEqual(1, num_islands([[1, 1, 1, 1, 0], [1, 1, 0, 1, 0], + [1, 1, 0, 0, 0], [0, 0, 0, 0, 0]])) + self.assertEqual(3, num_islands([[1, 1, 0, 0, 0], [1, 1, 0, 0, 0], + [0, 0, 1, 0, 0], [0, 0, 0, 1, 1]])) class TestPacificAtlantic(unittest.TestCase): def test_pacific_atlantic(self): - self.assertEqual([[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]], - pacific_atlantic([[1, 2, 2, 3, 5], [3, 2, 3, 4, 4], [2, 4, 5, 3, 1], [6, 7, 1, 4, 5], [5, 1, 1, 2, 4]])) + self.assertEqual([[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], + [3, 1], [4, 0]], pacific_atlantic([[1, 2, 2, 3, 5], + [3, 2, 3, 4, 4], + [2, 4, 5, 3, 1], + [6, 7, 1, 4, 5], + [5, 1, 1, 2, 4]])) class TestSudoku(unittest.TestCase): def test_sudoku_solver(self): - board = [["5","3","."], ["6",".", "."],[".","9","8"]] + board = [["5", "3", "."], ["6", ".", "."], [".", "9", "8"]] test_obj = Sudoku(board, 3, 3) test_obj.solve() - self.assertEqual([['5', '3', '1'], ['6', '1', '2'], ['1', '9', '8']],test_obj.board) + self.assertEqual([['5', '3', '1'], ['6', '1', '2'], + ['1', '9', '8']], test_obj.board) class TestWallsAndGates(unittest.TestCase): def test_walls_and_gates(self): - rooms = [[float("inf"), -1, 0, float("inf")], - [float("inf"), float("inf"), float("inf"), -1], - [float("inf"), -1, float("inf"), -1], - [0, -1, float("inf"), float("inf")]] + rooms = [[float("inf"), -1, 0, float("inf")], + [float("inf"), float("inf"), float("inf"), -1], + [float("inf"), -1, float("inf"), -1], + [0, -1, float("inf"), float("inf")]] walls_and_gates(rooms) - self.assertEqual([[3, -1, 0, 1], [2, 2, 1, -1], [1, -1, 2, -1], [0, -1, 3, 4]], rooms) + self.assertEqual([[3, -1, 0, 1], [2, 2, 1, -1], [1, -1, 2, -1], + [0, -1, 3, 4]], rooms) + class TestMazeSearch(unittest.TestCase): def test_maze_search(self): - maze_1 = [[1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1], [1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,0,1]] + maze_1 = [[1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, + 1, 0, 1, 1, 1], + [1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, + 0, 1, 1, 1, 0, 1]] self.assertEqual(37, find_path(maze_1)) - maze_2 = [[1,0,1,1,1,1], [1,0,1,0,1,0], [1,0,1,0,1,1], [1,1,1,0,1,1]] + maze_2 = [[1, 0, 1, 1, 1, 1], [1, 0, 1, 0, 1, 0], + [1, 0, 1, 0, 1, 1], [1, 1, 1, 0, 1, 1]] self.assertEqual(14, find_path(maze_2)) - maze_3 = [[1,0,0], [0,1,1], [0,1,1]] + maze_3 = [[1, 0, 0], [0, 1, 1], [0, 1, 1]] self.assertEqual(-1, find_path(maze_3)) + if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/test_dp.py b/tests/test_dp.py index 300be3d85..e8dca0bce 100644 --- a/tests/test_dp.py +++ b/tests/test_dp.py @@ -11,9 +11,7 @@ Job, schedule, Item, get_maximum_value, longest_increasing_subsequence, - longest_increasing_subsequence_optimized, - longest_increasing_subsequence_optimized2, - int_divide,find_k_factor, + int_divide, find_k_factor, planting_trees ) @@ -85,7 +83,7 @@ def test_fib_iter(self): class TestHosoyaTriangle(unittest.TestCase): """[summary] Test for the file hosoya_triangle - + Arguments: unittest {[type]} -- [description] """ @@ -109,7 +107,7 @@ def test_hosoya(self): 21, 13, 16, 15, 15, 16, 13, 21, 34, 21, 26, 24, 25, 24, 26, 21, 34, 55, 34, 42, 39, 40, 40, 39, 42, 34, 55], - hosoya_testing(10)) + hosoya_testing(10)) class TestHouseRobber(unittest.TestCase): @@ -129,7 +127,8 @@ def test_get_maximum_value(self): self.assertEqual(220, get_maximum_value([item1, item2, item3], 50)) item1, item2, item3, item4 = Item(60, 5), Item(50, 3), Item(70, 4), Item(30, 2) - self.assertEqual(80, get_maximum_value([item1, item2, item3, item4], 5)) + self.assertEqual(80, get_maximum_value([item1, item2, item3, item4], + 5)) class TestLongestIncreasingSubsequence(unittest.TestCase): @@ -156,32 +155,34 @@ def test_int_divide(self): self.assertEqual(42, int_divide(10)) self.assertEqual(204226, int_divide(50)) + class Test_dp_K_Factor(unittest.TestCase): def test_kfactor(self): - #Test 1 - n1=4 - k1=1 - self.assertEqual(find_k_factor(n1,k1),1) - - #Test 2 - n2=7 - k2=1 - self.assertEqual(find_k_factor(n2,k2),70302) - - #Test 3 - n3=10 - k3=2 - self.assertEqual(find_k_factor(n3,k3),74357) - - #Test 4 - n4=8 - k4=2 - self.assertEqual(find_k_factor(n4,k4),53) - - #Test 5 - n5=9 - k5=1 - self.assertEqual(find_k_factor(n5,k5),71284044) + # Test 1 + n1 = 4 + k1 = 1 + self.assertEqual(find_k_factor(n1, k1), 1) + + # Test 2 + n2 = 7 + k2 = 1 + self.assertEqual(find_k_factor(n2, k2), 70302) + + # Test 3 + n3 = 10 + k3 = 2 + self.assertEqual(find_k_factor(n3, k3), 74357) + + # Test 4 + n4 = 8 + k4 = 2 + self.assertEqual(find_k_factor(n4, k4), 53) + + # Test 5 + n5 = 9 + k5 = 1 + self.assertEqual(find_k_factor(n5, k5), 71284044) + class TestPlantingTrees(unittest.TestCase): def test_simple(self): @@ -208,5 +209,6 @@ def test_simple2(self): # assert self.assertEqual(res, 9.28538328578604) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_graph.py b/tests/test_graph.py index 325b5d896..8caf858e7 100644 --- a/tests/test_graph.py +++ b/tests/test_graph.py @@ -8,7 +8,6 @@ from algorithms.graph import maximum_flow_dfs from algorithms.graph import all_pairs_shortest_path from algorithms.graph import bellman_ford -from algorithms.graph import bellman_ford from algorithms.graph import count_connected_number_of_component from algorithms.graph import prims_minimum_spanning @@ -37,7 +36,8 @@ def test_tarjan_example_1(self): } g = Tarjan(example) - self.assertEqual(g.sccs, [['F', 'G'], ['C', 'D', 'H'], ['A', 'B', 'E']]) + self.assertEqual(g.sccs, [['F', 'G'], ['C', 'D', 'H'], + ['A', 'B', 'E']]) def test_tarjan_example_2(self): # Graph from https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm#/media/File:Tarjan%27s_Algorithm_Animation.gif @@ -53,39 +53,36 @@ def test_tarjan_example_2(self): } g = Tarjan(example) - self.assertEqual(g.sccs, [['A', 'B', 'E'], ['C', 'D'], ['F', 'G'], ['H']]) + self.assertEqual(g.sccs, [['A', 'B', 'E'], ['C', 'D'], ['F', 'G'], + ['H']]) + - class TestCheckBipartite(unittest.TestCase): - def test_check_bipartite(self): - adj_list_1 = [[0, 0, 1], [0, 0, 1], [1, 1, 0]] self.assertEqual(True, check_bipartite(adj_list_1)) - adj_list_2 = [[0, 1, 0, 1], [1, 0, 1, 0], [0, 1, 0, 1], [1, 0, 1, 0]] self.assertEqual(True, check_bipartite(adj_list_2)) - adj_list_3 = [[0, 1, 0, 0], [1, 0, 1, 1], [0, 1, 0, 1], [0, 1, 1, 0]] self.assertEqual(False, check_bipartite(adj_list_3)) + class TestDijkstra(unittest.TestCase): - def test_dijkstra(self): - g = Dijkstra(9) - g.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] - ]; + g = Dijkstra(9) + g.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]] self.assertEqual(g.dijkstra(0), [0, 4, 12, 19, 21, 11, 9, 8, 14]) + class TestMaximumFlow(unittest.TestCase): """ Test for the file maximum_flow.py @@ -104,6 +101,7 @@ def test_ford_fulkerson(self): [0, 0, 0, 0, 0, 0, 0] ] self.assertEqual(19, ford_fulkerson(capacity, 0, 6)) + def test_edmonds_karp(self): capacity = [ [0, 10, 10, 0, 0, 0, 0], @@ -115,6 +113,7 @@ def test_edmonds_karp(self): [0, 0, 0, 0, 0, 0, 0] ] self.assertEqual(19, edmonds_karp(capacity, 0, 6)) + def dinic(self): capacity = [ [0, 10, 10, 0, 0, 0, 0], @@ -127,6 +126,7 @@ def dinic(self): ] self.assertEqual(19, dinic(capacity, 0, 6)) + class TestMaximum_Flow_Bfs(unittest.TestCase): """ @@ -134,13 +134,12 @@ class TestMaximum_Flow_Bfs(unittest.TestCase): Arguments: unittest {[type]} -- [description] """ - def test_maximum_flow_bfs(self): 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, 4, 0, 0, 14, 0], + [0, 0, 9, 0, 0, 20], [0, 0, 0, 7, 0, 4], [0, 0, 0, 0, 0, 0] ] @@ -148,6 +147,7 @@ def test_maximum_flow_bfs(self): self.assertEqual(maximum_flow, 23) + class TestMaximum_Flow_Dfs(unittest.TestCase): """ @@ -155,13 +155,12 @@ class TestMaximum_Flow_Dfs(unittest.TestCase): Arguments: unittest {[type]} -- [description] """ - def test_maximum_flow_dfs(self): 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, 4, 0, 0, 14, 0], + [0, 0, 9, 0, 0, 20], [0, 0, 0, 7, 0, 4], [0, 0, 0, 0, 0, 0] ] @@ -171,24 +170,26 @@ def test_maximum_flow_dfs(self): class TestAll_Pairs_Shortest_Path(unittest.TestCase): - def test_all_pairs_shortest_path(self): - graph = [[0, 0.1, 0.101, 0.142, 0.277], - [0.465, 0, 0.191, 0.192, 0.587], - [0.245, 0.554, 0, 0.333, 0.931], - [1.032, 0.668, 0.656, 0, 0.151], + graph = [[0, 0.1, 0.101, 0.142, 0.277], + [0.465, 0, 0.191, 0.192, 0.587], + [0.245, 0.554, 0, 0.333, 0.931], + [1.032, 0.668, 0.656, 0, 0.151], [0.867, 0.119, 0.352, 0.398, 0]] result = all_pairs_shortest_path(graph) self.assertEqual(result, [ - [0, 0.1, 0.101, 0.142, 0.277], - [0.436, 0, 0.191, 0.192, 0.34299999999999997], - [0.245, 0.345, 0, 0.333, 0.484], - [0.706, 0.27, 0.46099999999999997, 0, 0.151], - [0.5549999999999999, 0.119, 0.31, 0.311, 0], - ]) - - + [0, 0.1, 0.101, 0.142, 0.277], + [0.436, 0, 0.191, 0.192, + 0.34299999999999997], + [0.245, 0.345, 0, 0.333, 0.484], + [0.706, 0.27, 0.46099999999999997, 0, + 0.151], + [0.5549999999999999, 0.119, 0.31, 0.311, + 0], + ]) + + class TestBellmanFord(unittest.TestCase): def test_bellman_ford(self): graph1 = { @@ -198,90 +199,82 @@ def test_bellman_ford(self): 'd': {'a': 2, 'c': 7}, 'e': {'b': -3} } - self.assertEqual(True, bellman_ford(graph1, 'a')) - graph2 = { 'a': {'d': 3, 'e': 4}, - 'b': {'a': 7, 'e':2}, - 'c': {'a': 12, 'd':9, 'e':11}, + 'b': {'a': 7, 'e': 2}, + 'c': {'a': 12, 'd': 9, 'e': 11}, 'd': {'c': 5, 'e': 11}, 'e': {'a': 7, 'b': 5, 'd': 1} - } - + } self.assertEqual(True, bellman_ford(graph2, 'a')) - -class TestConnectedComponentInGraph(unittest.TestCase): + +class TestConnectedComponentInGraph(unittest.TestCase): """ Class for testing different cases for connected components in graph """ def test_count_connected_components(self): """ - Test Function that test the different cases of count connected components - + Test Function that test the different cases of count connected + components 2----------0 1--------5 3 | | 4 - output = 3 """ expected_result = 3 - # adjacency list representation of graph l = [[2], - [5], - [0,4], - [], - [2], - [1] - ] + [5], + [0,4], + [], + [2], + [1]] size = 5 - result = count_connected_number_of_component.count_components(l,size) - self.assertEqual(result,expected_result) + result = count_connected_number_of_component.count_components(l, size) + self.assertEqual(result, expected_result) def test_connected_components_with_empty_graph(self): """ - input : + input : output : 0 """ - l = [[]] expected_result = 0 size = 0 - result = count_connected_number_of_component.count_components(l,size) - self.assertEqual(result,expected_result) + result = count_connected_number_of_component.count_components(l, size) + self.assertEqual(result, expected_result) def test_connected_components_without_edges_graph(self): """ input : 0 2 3 4 output : 4 - """ - l = [[0],[],[2],[3],[4]] + """ + l = [[0], [], [2], [3], [4]] size = 4 expected_result = 4 - result = count_connected_number_of_component.count_components(l,size) - self.assertEqual(result,expected_result) + result = count_connected_number_of_component.count_components(l, size) + self.assertEqual(result, expected_result) + - class PrimsMinimumSpanning(unittest.TestCase): def test_prim_spanning(self): graph1 = { - 1 : [ [3, 2], [8, 3] ], - 2 : [ [3, 1], [5, 4] ], - 3 : [ [8, 1], [2, 4], [4, 5] ], - 4 : [ [5, 2], [2, 3], [6, 5] ], - 5 : [ [4, 3], [6, 4] ] + 1: [[3, 2], [8, 3]], + 2: [[3, 1], [5, 4]], + 3: [[8, 1], [2, 4], [4, 5]], + 4: [[5, 2], [2, 3], [6, 5]], + 5: [[4, 3], [6, 4]] } self.assertEqual(14, prims_minimum_spanning(graph1)) - graph2 = { - 1 : [ [7, 2], [6, 4] ], - 2 : [ [7, 1], [9, 4], [6, 3] ], - 3 : [ [8, 4], [6, 2] ], - 4 : [ [6, 1], [9, 2], [8, 3] ] + 1: [[7, 2], [6, 4]], + 2: [[7, 1], [9, 4], [6, 3]], + 3: [[8, 4], [6, 2]], + 4: [[6, 1], [9, 2], [8, 3]] } self.assertEqual(19, prims_minimum_spanning(graph2)) diff --git a/tests/test_heap.py b/tests/test_heap.py index 6814c6196..11e323e5f 100644 --- a/tests/test_heap.py +++ b/tests/test_heap.py @@ -55,10 +55,12 @@ def test_max_sliding_window(self): self.assertEqual([3, 3, 5, 5, 6, 7], max_sliding_window(nums, 3)) def test_k_closest_points(self): - points = [(1, 0), (2, 3), (5, 2), (1, 1), (2, 8), (10, 2), (-1, 0), (-2, -2)] + points = [(1, 0), (2, 3), (5, 2), (1, 1), (2, 8), (10, 2), + (-1, 0), (-2, -2)] self.assertEqual([(-1, 0), (1, 0)], k_closest(points, 2)) self.assertEqual([(1, 1), (-1, 0), (1, 0)], k_closest(points, 3)) - self.assertEqual([(-2, -2), (1, 1), (1, 0), (-1, 0)], k_closest(points, 4)) + self.assertEqual([(-2, -2), (1, 1), (1, 0), + (-1, 0)], k_closest(points, 4)) self.assertEqual([(10, 2), (2, 8), (5, 2), (-2, -2), (2, 3), (1, 0), (-1, 0), (1, 1)], k_closest(points, 8)) diff --git a/tests/test_iterative_segment_tree.py b/tests/test_iterative_segment_tree.py index b9a611a05..77c0283ba 100644 --- a/tests/test_iterative_segment_tree.py +++ b/tests/test_iterative_segment_tree.py @@ -21,10 +21,14 @@ def test_segment_tree_creation(self): min_segment_tree = SegmentTree(arr, min) sum_segment_tree = SegmentTree(arr, lambda a, b: a + b) gcd_segment_tree = SegmentTree(arr, gcd) - self.assertEqual(max_segment_tree.tree, [None, 9, 8, 9, 4, 8, 9, 2, 4, 3, 6, 8, 9, 3]) - self.assertEqual(min_segment_tree.tree, [None, 2, 3, 2, 3, 6, 3, 2, 4, 3, 6, 8, 9, 3]) - self.assertEqual(sum_segment_tree.tree, [None, 35, 21, 14, 7, 14, 12, 2, 4, 3, 6, 8, 9, 3]) - self.assertEqual(gcd_segment_tree.tree, [None, 1, 1, 1, 1, 2, 3, 2, 4, 3, 6, 8, 9, 3]) + self.assertEqual(max_segment_tree.tree, + [None, 9, 8, 9, 4, 8, 9, 2, 4, 3, 6, 8, 9, 3]) + self.assertEqual(min_segment_tree.tree, + [None, 2, 3, 2, 3, 6, 3, 2, 4, 3, 6, 8, 9, 3]) + self.assertEqual(sum_segment_tree.tree, + [None, 35, 21, 14, 7, 14, 12, 2, 4, 3, 6, 8, 9, 3]) + self.assertEqual(gcd_segment_tree.tree, + [None, 1, 1, 1, 1, 2, 3, 2, 4, 3, 6, 8, 9, 3]) def test_max_segment_tree(self): arr = [-1, 1, 10, 2, 9, -3, 8, 4, 7, 5, 6, 0] @@ -44,22 +48,26 @@ def test_gcd_segment_tree(self): def test_max_segment_tree_with_updates(self): arr = [-1, 1, 10, 2, 9, -3, 8, 4, 7, 5, 6, 0] - updates = {0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10, 10: 11, 11: 12} + updates = {0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, + 9: 10, 10: 11, 11: 12} self.__test_all_segments_with_updates(arr, max, updates) def test_min_segment_tree_with_updates(self): arr = [1, 10, -2, 9, -3, 8, 4, -7, 5, 6, 11, -12] - 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} + 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} self.__test_all_segments_with_updates(arr, min, updates) def test_sum_segment_tree_with_updates(self): arr = [1, 10, 2, 9, 3, 8, 4, 7, 5, 6, -11, -12] - updates = {0: 12, 1: 11, 2: 10, 3: 9, 4: 8, 5: 7, 6: 6, 7: 5, 8: 4, 9: 3, 10: 2, 11: 1} + updates = {0: 12, 1: 11, 2: 10, 3: 9, 4: 8, 5: 7, 6: 6, 7: 5, 8: 4, + 9: 3, 10: 2, 11: 1} self.__test_all_segments_with_updates(arr, lambda a, b: a + b, updates) def test_gcd_segment_tree_with_updates(self): arr = [1, 10, 2, 9, 3, 8, 4, 7, 5, 6, 11, 12, 14] - updates = {0: 4, 1: 2, 2: 3, 3: 9, 4: 21, 5: 7, 6: 4, 7: 4, 8: 2, 9: 5, 10: 17, 11: 12, 12: 3} + updates = {0: 4, 1: 2, 2: 3, 3: 9, 4: 21, 5: 7, 6: 4, 7: 4, 8: 2, + 9: 5, 10: 17, 11: 12, 12: 3} self.__test_all_segments_with_updates(arr, gcd, updates) def __test_all_segments(self, arr, fnc): diff --git a/tests/test_map.py b/tests/test_map.py index c8164881b..a62656d15 100644 --- a/tests/test_map.py +++ b/tests/test_map.py @@ -1,6 +1,6 @@ from algorithms.map import ( HashTable, ResizableHashTable, - Node, SeparateChainingHashTable, + SeparateChainingHashTable, word_pattern, is_isomorphic, is_anagram, @@ -9,6 +9,7 @@ import unittest + class TestHashTable(unittest.TestCase): def test_one_entry(self): m = HashTable(10) @@ -45,7 +46,7 @@ def test_delete_key(self): m.put(i, i**2) m.del_(1) self.assertEqual(None, m.get(1)) - self.assertEqual(4,m.get(2)) + self.assertEqual(4, m.get(2)) def test_delete_key_and_reassign(self): m = HashTable(10) @@ -91,9 +92,9 @@ def test_resizable_hash_table(self): def test_fill_up_the_limit(self): m = HashTable(10) for i in range(10): - m.put(i,i**2) + m.put(i, i**2) for i in range(10): - self.assertEqual(i**2,m.get(i)) + self.assertEqual(i**2, m.get(i)) class TestSeparateChainingHashTable(unittest.TestCase): @@ -165,18 +166,20 @@ def test_is_isomorphic(self): self.assertFalse(is_isomorphic("foo", "bar")) self.assertTrue(is_isomorphic("paper", "title")) + class TestLongestPalindromicSubsequence(unittest.TestCase): def test_longest_palindromic_subsequence_is_correct(self): - self.assertEqual(3,longest_palindromic_subsequence('BBABCBCAB')) - self.assertEqual(4,longest_palindromic_subsequence('abbaeae')) - self.assertEqual(7,longest_palindromic_subsequence('babbbababaa')) - self.assertEqual(4,longest_palindromic_subsequence('daccandeeja')) + self.assertEqual(3, longest_palindromic_subsequence('BBABCBCAB')) + self.assertEqual(4, longest_palindromic_subsequence('abbaeae')) + self.assertEqual(7, longest_palindromic_subsequence('babbbababaa')) + self.assertEqual(4, longest_palindromic_subsequence('daccandeeja')) def test_longest_palindromic_subsequence_is_incorrect(self): - self.assertNotEqual(4,longest_palindromic_subsequence('BBABCBCAB')) - self.assertNotEqual(5,longest_palindromic_subsequence('abbaeae')) - self.assertNotEqual(2,longest_palindromic_subsequence('babbbababaa')) - self.assertNotEqual(1,longest_palindromic_subsequence('daccandeeja')) + self.assertNotEqual(4, longest_palindromic_subsequence('BBABCBCAB')) + self.assertNotEqual(5, longest_palindromic_subsequence('abbaeae')) + self.assertNotEqual(2, longest_palindromic_subsequence('babbbababaa')) + self.assertNotEqual(1, longest_palindromic_subsequence('daccandeeja')) + class TestIsAnagram(unittest.TestCase): def test_is_anagram(self): @@ -184,7 +187,5 @@ def test_is_anagram(self): self.assertFalse(is_anagram("rat", "car")) - - if __name__ == "__main__": unittest.main() diff --git a/tests/test_maths.py b/tests/test_maths.py index 967cf64d9..ec8f65798 100644 --- a/tests/test_maths.py +++ b/tests/test_maths.py @@ -22,9 +22,7 @@ magic_number, find_order, find_primitive_root, - alice_private_key, alice_public_key, bob_private_key, bob_public_key, alice_shared_key, bob_shared_key, diffie_hellman_key_exchange, num_digits, - alice_private_key, alice_public_key, bob_private_key, bob_public_key, alice_shared_key, bob_shared_key, diffie_hellman_key_exchange, krishnamurthy_number, chinese_remainder_theorem, ) @@ -131,13 +129,15 @@ def test_gcd(self): self.assertEqual(1, gcd(13, 17)) def test_gcd_non_integer_input(self): - with pytest.raises(ValueError, match=r"Input arguments are not integers"): + with pytest.raises(ValueError, + match=r"Input arguments are not integers"): gcd(1.0, 5) gcd(5, 6.7) gcd(33.8649, 6.12312312) def test_gcd_zero_input(self): - with pytest.raises(ValueError, match=r"One or more input arguments equals zero"): + with pytest.raises(ValueError, + match=r"One or more input arguments equals zero"): gcd(0, 12) gcd(12, 0) gcd(0, 0) @@ -157,7 +157,8 @@ def test_lcm_negative_numbers(self): self.assertEqual(1, lcm(-1, 1)) def test_lcm_zero_input(self): - with pytest.raises(ValueError, match=r"One or more input arguments equals zero"): + with pytest.raises(ValueError, + match=r"One or more input arguments equals zero"): lcm(0, 12) lcm(12, 0) lcm(0, 0) @@ -171,7 +172,6 @@ def test_gcd_bit(self): self.assertEqual(1, gcd(13, 17)) - class TestGenerateStroboGrammatic(unittest.TestCase): """[summary] Test for the file generate_strobogrammatic.py @@ -216,7 +216,8 @@ def test_modular_inverse(self): # checks if x * x_inv == 1 (mod m) self.assertEqual(1, 2 * modular_inverse.modular_inverse(2, 19) % 19) self.assertEqual(1, 53 * modular_inverse.modular_inverse(53, 91) % 91) - self.assertEqual(1, 2 * modular_inverse.modular_inverse(2, 1000000007) % 1000000007) + self.assertEqual(1, 2 * modular_inverse.modular_inverse(2, 1000000007) + % 1000000007) self.assertRaises(ValueError, modular_inverse.modular_inverse, 2, 20) @@ -295,7 +296,8 @@ class TestPythagoras(unittest.TestCase): """ def test_pythagoras(self): - self.assertEqual("Hypotenuse = 3.605551275463989", pythagoras(3, 2, "?")) + self.assertEqual("Hypotenuse = 3.605551275463989", + pythagoras(3, 2, "?")) class TestRabinMiller(unittest.TestCase): @@ -417,7 +419,8 @@ def test_find_primitive_root_simple(self): self.assertListEqual([0], find_primitive_root(1)) self.assertListEqual([2, 3], find_primitive_root(5)) self.assertListEqual([], find_primitive_root(24)) - self.assertListEqual([2, 5, 13, 15, 17, 18, 19, 20, 22, 24, 32, 35], find_primitive_root(37)) + self.assertListEqual([2, 5, 13, 15, 17, 18, 19, 20, 22, 24, 32, 35], + find_primitive_root(37)) class TestFindOrder(unittest.TestCase): @@ -434,6 +437,7 @@ def test_find_order_simple(self): self.assertEqual(-1, find_order(128, 256)) self.assertEqual(352, find_order(3, 353)) + class TestKrishnamurthyNumber(unittest.TestCase): """[summary] Test for the file krishnamurthy_number.py @@ -441,7 +445,7 @@ class TestKrishnamurthyNumber(unittest.TestCase): Arguments: unittest {[type]} -- [description] """ - + def test_krishnamurthy_number(self): self.assertFalse(krishnamurthy_number(0)) self.assertTrue(krishnamurthy_number(2)) @@ -490,12 +494,13 @@ class TestNumberOfDigits(unittest.TestCase): unittest {[type]} -- [description] """ def test_num_digits(self): - self.assertEqual(2,num_digits(12)) - self.assertEqual(5,num_digits(99999)) - self.assertEqual(1,num_digits(8)) - self.assertEqual(1,num_digits(0)) - self.assertEqual(1,num_digits(-5)) - self.assertEqual(3,num_digits(-254)) + self.assertEqual(2, num_digits(12)) + self.assertEqual(5, num_digits(99999)) + self.assertEqual(1, num_digits(8)) + self.assertEqual(1, num_digits(0)) + self.assertEqual(1, num_digits(-5)) + self.assertEqual(3, num_digits(-254)) + class TestChineseRemainderSolver(unittest.TestCase): def test_k_three(self): @@ -504,7 +509,8 @@ def test_k_three(self): # solves the system of equations num = [3, 7, 10] rem = [2, 3, 3] - self.assertEqual(chinese_remainder_theorem.solve_chinese_remainder(num, rem), 143) + self.assertEqual(chinese_remainder_theorem. + solve_chinese_remainder(num, rem), 143) def test_k_five(self): # Example which should give the answer 3383 @@ -512,7 +518,8 @@ def test_k_five(self): # solves the system of equations num = [3, 5, 7, 11, 26] rem = [2, 3, 2, 6, 3] - self.assertEqual(chinese_remainder_theorem.solve_chinese_remainder(num, rem), 3383) + self.assertEqual(chinese_remainder_theorem. + solve_chinese_remainder(num, rem), 3383) def test_exception_non_coprime(self): # There should be an exception when all @@ -528,5 +535,6 @@ def test_empty_lists(self): with self.assertRaises(Exception): chinese_remainder_theorem.solve_chinese_remainder(num, rem) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_matrix.py b/tests/test_matrix.py index 667dc1670..262fdbe62 100644 --- a/tests/test_matrix.py +++ b/tests/test_matrix.py @@ -51,16 +51,20 @@ class TestCopyTransform(unittest.TestCase): def test_copy_transform(self): self.assertEqual(copy_transform.rotate_clockwise( - [[1, 2, 3], [4, 5, 6], [7, 8, 9]]), [[7, 4, 1], [8, 5, 2], [9, 6, 3]]) + [[1, 2, 3], [4, 5, 6], [7, 8, 9]]), + [[7, 4, 1], [8, 5, 2], [9, 6, 3]]) self.assertEqual(copy_transform.rotate_counterclockwise( - [[1, 2, 3], [4, 5, 6], [7, 8, 9]]), [[3, 6, 9], [2, 5, 8], [1, 4, 7]]) + [[1, 2, 3], [4, 5, 6], [7, 8, 9]]), + [[3, 6, 9], [2, 5, 8], [1, 4, 7]]) self.assertEqual(copy_transform.top_left_invert( - [[1, 2, 3], [4, 5, 6], [7, 8, 9]]), [[1, 4, 7], [2, 5, 8], [3, 6, 9]]) + [[1, 2, 3], [4, 5, 6], [7, 8, 9]]), + [[1, 4, 7], [2, 5, 8], [3, 6, 9]]) self.assertEqual(copy_transform.bottom_left_invert( - [[1, 2, 3], [4, 5, 6], [7, 8, 9]]), [[9, 6, 3], [8, 5, 2], [7, 4, 1]]) + [[1, 2, 3], [4, 5, 6], [7, 8, 9]]), + [[9, 6, 3], [8, 5, 2], [7, 4, 1]]) class TestCroutMatrixDecomposition(unittest.TestCase): @@ -95,7 +99,8 @@ def test_crout_matrix_decomposition(self): [0, 0, 1.0, 0.0], [0, 0, 0, 1.0]]), crout_matrix_decomposition.crout_matrix_decomposition( - [[2, 1, 3, 1], [4, 1, 4, 1], [6, 1, 7, 1], [8, 1, 9, 1]])) + [[2, 1, 3, 1], [4, 1, 4, 1], [6, 1, 7, 1], + [8, 1, 9, 1]])) class TestCholeskyMatrixDecomposition(unittest.TestCase): @@ -119,14 +124,16 @@ def test_cholesky_matrix_decomposition(self): self.assertEqual(None, cholesky_matrix_decomposition.cholesky_decomposition( - [[4, 12, -16], [12, 37, -43], [-16, -43, 98], [1, 2, 3]])) + [[4, 12, -16], [12, 37, -43], [-16, -43, 98], + [1, 2, 3]])) # example taken from https://ece.uwaterloo.ca/~dwharder/NumericalAnalysis/04LinearAlgebra/cholesky/ self.assertEqual([[2.23606797749979, 0.0, 0.0, 0.0], [0.5366563145999494, 2.389979079406345, 0.0, 0.0], [0.13416407864998736, -0.19749126846635062, 2.818332343581848, 0.0], - [-0.2683281572999747, 0.43682390737048743, 0.64657701271919, 3.052723872310221]], + [-0.2683281572999747, 0.43682390737048743, + 0.64657701271919, 3.052723872310221]], cholesky_matrix_decomposition.cholesky_decomposition( [[5, 1.2, 0.3, -0.6], [1.2, 6, -0.4, 0.9], [0.3, -0.4, 8, 1.7], [-0.6, 0.9, 1.7, 10]])) @@ -160,11 +167,16 @@ def test_inversion(self): self.assertEqual(matrix_inversion.invert_matrix(m5), [[-4]]) m6 = [[3, 5, 1], [2, 5, 0], [1, 9, 8]] - self.assertEqual(matrix_inversion.invert_matrix(m6), [[Fraction(40, 53), Fraction(-31, 53), Fraction(-5, 53)], - [Fraction(-16, 53), Fraction( - 23, 53), Fraction(2, 53)], - [Fraction(13, 53), Fraction(-22, 53), Fraction(5, 53)]]) - + self.assertEqual(matrix_inversion.invert_matrix(m6), + [[Fraction(40, 53), + Fraction(-31, 53), + Fraction(-5, 53)], + [Fraction(-16, 53), + Fraction(23, 53), + Fraction(2, 53)], + [Fraction(13, 53), + Fraction(-22, 53), + Fraction(5, 53)]]) class TestMatrixExponentiation(unittest.TestCase): @@ -179,16 +191,16 @@ def test_matrix_exponentiation(self): mat = [[1, 0, 2], [2, 1, 0], [0, 2, 1]] self.assertEqual(matrix_exponentiation.matrix_exponentiation(mat, 0), - [[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + [[1, 0, 0], [0, 1, 0], [0, 0, 1]]) self.assertEqual(matrix_exponentiation.matrix_exponentiation(mat, 1), - [[1, 0, 2], [2, 1, 0], [0, 2, 1]]) + [[1, 0, 2], [2, 1, 0], [0, 2, 1]]) self.assertEqual(matrix_exponentiation.matrix_exponentiation(mat, 2), - [[1, 4, 4], [4, 1, 4], [4, 4, 1]]) + [[1, 4, 4], [4, 1, 4], [4, 4, 1]]) self.assertEqual(matrix_exponentiation.matrix_exponentiation(mat, 5), - [[81, 72, 90], [90, 81, 72], [72, 90, 81]]) + [[81, 72, 90], [90, 81, 72], [72, 90, 81]]) class TestMultiply(unittest.TestCase): @@ -214,7 +226,8 @@ class TestRotateImage(unittest.TestCase): def test_rotate_image(self): self.assertEqual(rotate_image.rotate( - [[1, 2, 3], [4, 5, 6], [7, 8, 9]]), [[7, 4, 1], [8, 5, 2], [9, 6, 3]]) + [[1, 2, 3], [4, 5, 6], [7, 8, 9]]), + [[7, 4, 1], [8, 5, 2], [9, 6, 3]]) class TestSparseDotVector(unittest.TestCase): @@ -226,8 +239,12 @@ class TestSparseDotVector(unittest.TestCase): """ def test_sparse_dot_vector(self): - self.assertEqual(sparse_dot_vector.dot_product(sparse_dot_vector.vector_to_index_value_list( - [1., 2., 3.]), sparse_dot_vector.vector_to_index_value_list([0., 2., 2.])), 10) + self.assertEqual(sparse_dot_vector. + dot_product(sparse_dot_vector. + vector_to_index_value_list([1., 2., 3.]), + sparse_dot_vector. + vector_to_index_value_list([0., 2., 2.])), + 10) class TestSpiralTraversal(unittest.TestCase): diff --git a/tests/test_ml.py b/tests/test_ml.py index 61852ec31..1622ae1a8 100644 --- a/tests/test_ml.py +++ b/tests/test_ml.py @@ -5,27 +5,36 @@ import unittest + class TestML(unittest.TestCase): def setUp(self): # train set for the AND-function - self.trainSetAND = {(0,0) : 0, (0,1) :0, (1,0) : 0, (1,1) : 1} + self.trainSetAND = {(0, 0): 0, (0, 1): 0, (1, 0): 0, (1, 1): 1} # train set for light or dark colors - self.trainSetLight = {(11, 98, 237) : 'L', (3, 39, 96) : 'D', (242, 226, 12) : 'L', (99, 93, 4) : 'D', - (232, 62, 32) : 'L', (119, 28, 11) : 'D', (25, 214, 47) : 'L', (89, 136, 247) : 'L', - (21, 34, 63) : 'D', (237, 99, 120) : 'L', (73, 33, 39) : 'D'} + self.trainSetLight = {(11, 98, 237): 'L', (3, 39, 96): 'D', + (242, 226, 12): 'L', (99, 93, 4): 'D', + (232, 62, 32): 'L', (119, 28, 11): 'D', + (25, 214, 47): 'L', (89, 136, 247): 'L', + (21, 34, 63): 'D', (237, 99, 120): 'L', + (73, 33, 39): 'D'} + def test_nearest_neighbor(self): # AND-function - self.assertEqual(nearest_neighbor((1,1), self.trainSetAND), 1) - self.assertEqual(nearest_neighbor((0,1), self.trainSetAND), 0) + self.assertEqual(nearest_neighbor((1, 1), self.trainSetAND), 1) + self.assertEqual(nearest_neighbor((0, 1), self.trainSetAND), 0) # dark/light color test - self.assertEqual(nearest_neighbor((31, 242, 164), self.trainSetLight), 'L') - self.assertEqual(nearest_neighbor((13, 94, 64), self.trainSetLight), 'D') - self.assertEqual(nearest_neighbor((230, 52, 239), self.trainSetLight), 'L') + self.assertEqual(nearest_neighbor((31, 242, 164), + self.trainSetLight), 'L') + self.assertEqual(nearest_neighbor((13, 94, 64), + self.trainSetLight), 'D') + self.assertEqual(nearest_neighbor((230, 52, 239), + self.trainSetLight), 'L') + def test_distance(self): - self.assertAlmostEqual(distance((1,2,3), (1,0,-1)), 4.47, 2) + self.assertAlmostEqual(distance((1, 2, 3), (1, 0, -1)), 4.47, 2) + - if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/test_monomial.py b/tests/test_monomial.py index 8e4667d83..31e56f79b 100644 --- a/tests/test_monomial.py +++ b/tests/test_monomial.py @@ -5,8 +5,9 @@ import unittest + class TestSuite(unittest.TestCase): - + def setUp(self): self.m1 = Monomial({}) self.m2 = Monomial({1: 1}, 2) @@ -15,7 +16,7 @@ def setUp(self): self.m5 = Monomial({2: 1, 3: 0}, Fraction(2, 3)) self.m6 = Monomial({1: 0, 2: 0, 3: 0}, -2.27) self.m7 = Monomial({1: 2, 7: 2}, -math.pi) - self.m8 = Monomial({150: 5, 170: 2, 10000:3}, 0) + self.m8 = Monomial({150: 5, 170: 2, 10000: 3}, 0) self.m9 = 2 self.m10 = math.pi self.m11 = Fraction(3, 8) @@ -39,10 +40,11 @@ def test_monomial_addition(self): # Zero monomial + Zero monomial = Zero monomial self.assertEqual(self.m1 + self.m1, self.m1) - + # Coefficient float. - self.assertEqual(self.m7 + self.m7, Monomial({1: 2, 7: 2}, -2*math.pi)) - + self.assertEqual(self.m7 + self.m7, Monomial({1: 2, 7: 2}, + -2 * math.pi)) + # Coefficient 0 so should equal the zero monomial. self.assertEqual(self.m8, self.m1) @@ -77,13 +79,13 @@ def test_monomial_subtraction(self): # Zero monomial - Zero monomial = Zero monomial self.assertEqual(self.m1 - self.m1, self.m1) - + # Coefficient int. self.assertEqual(self.m2 - self.m15, Monomial({1: 1}, -1)) - - # Coefficient float. - self.assertEqual(self.m16 - self.m7, Monomial({1: 2, 7: 2}, 2*math.pi)) + # Coefficient float. + self.assertEqual(self.m16 - self.m7, Monomial({1: 2, 7: 2}, + 2 * math.pi)) # The constant term cannot be added to any monomial # that has any variables. @@ -103,7 +105,7 @@ def test_monomial_subtraction(self): return def test_monomial_multiplication(self): - + # Usual multiplication. # The positive and negative powers of the same variable # should cancel out. @@ -116,7 +118,8 @@ def test_monomial_multiplication(self): self.assertEqual(self.m1 * self.m2, self.m1) # Test usual float multiplication. - self.assertEqual(self.m7 * self.m3, Monomial({1: 4, 2: -1, 7: 2}, -1.5*math.pi)) + self.assertEqual(self.m7 * self.m3, Monomial({1: 4, 2: -1, 7: 2}, + -1.5*math.pi)) return @@ -125,10 +128,11 @@ def test_monomial_inverse(self): # The Zero monomial is not invertible. self.assertRaises(ValueError, lambda x: x.inverse(), self.m1) self.assertRaises(ValueError, lambda x: x.inverse(), self.m8) - self.assertRaises(ValueError, lambda x: x.inverse(), Monomial({},self.m12)) + self.assertRaises(ValueError, lambda x: x.inverse(), + Monomial({}, self.m12)) # Check some inverses. - self.assertEqual(self.m7.inverse(), Monomial({1: -2, 7: -2}, -1/math.pi)) + self.assertEqual(self.m7.inverse(), Monomial({1: -2, 7: -2}, -1 / math.pi)) # Doesn't matter if the coefficient is Fraction or float. # Both should be treated as same. @@ -142,31 +146,41 @@ def test_monomial_inverse(self): def test_monomial_division(self): # Any monomial divided by the Zero Monomial should raise a ValueError. - self.assertRaises(ValueError, lambda x, y: x.__truediv__(y), self.m2, self.m1) - self.assertRaises(ValueError, lambda x, y: x.__truediv__(y), self.m2, self.m8) - self.assertRaises(ValueError, lambda x, y: x.__truediv__(y), self.m2, self.m12) + self.assertRaises(ValueError, lambda x, y: x.__truediv__(y), + self.m2, self.m1) + self.assertRaises(ValueError, lambda x, y: x.__truediv__(y), + self.m2, self.m8) + self.assertRaises(ValueError, lambda x, y: x.__truediv__(y), + self.m2, self.m12) # Test some usual cases. - self.assertEqual(self.m7 / self.m3, Monomial({2: 1, 7: 2}, -2*math.pi/3)) + self.assertEqual(self.m7 / self.m3, Monomial({2: 1, 7: 2}, + -2 * math.pi / 3)) self.assertEqual(self.m14 / self.m13, Monomial({1: 1}) * Fraction(-3, 2)) return def test_monomial_substitution(self): # Test with int. - self.assertAlmostEqual(self.m7.substitute(2), -16*math.pi, delta=1e-9) + self.assertAlmostEqual(self.m7.substitute(2), -16 * math.pi, delta=1e-9) # Test with float. - self.assertAlmostEqual(self.m7.substitute(1.5), (1.5 ** 4)* -math.pi, delta=1e-9) + self.assertAlmostEqual(self.m7.substitute(1.5), (1.5 ** 4) * -math.pi, + delta=1e-9) # Test with Fraction. - self.assertAlmostEqual(self.m7.substitute(Fraction(-1, 2)), (Fraction(-1, 2) ** 4)*-math.pi, delta=1e-9) + self.assertAlmostEqual(self.m7.substitute(Fraction(-1, 2)), + (Fraction(-1, 2) ** 4)*-math.pi, delta=1e-9) # Test with a complete substitution map. - self.assertAlmostEqual(self.m7.substitute({1: 3, 7: 0}), (3 ** 2) * (0 ** 2) * -math.pi, delta=1e-9) + self.assertAlmostEqual(self.m7.substitute({1: 3, 7: 0}), + (3 ** 2) * (0 ** 2) * -math.pi, delta=1e-9) # Test with a more than complete substitution map. - self.assertAlmostEqual(self.m7.substitute({1: 3, 7: 0, 2: 2}), (3 ** 2) * (0 ** 2) * -math.pi, delta=1e-9) - + self.assertAlmostEqual(self.m7.substitute({1: 3, 7: 0, 2: 2}), + (3 ** 2) * (0 ** 2) * -math.pi, delta=1e-9) + # Should raise a ValueError if not enough variables are supplied! - self.assertRaises(ValueError, lambda x, y: x.substitute(y), self.m7, {1: 3, 2: 2}) - self.assertRaises(ValueError, lambda x, y: x.substitute(y), self.m7, {2: 2}) + self.assertRaises(ValueError, lambda x, y: x.substitute(y), self.m7, + {1: 3, 2: 2}) + self.assertRaises(ValueError, lambda x, y: x.substitute(y), self.m7, + {2: 2}) # The zero monomial always gives zero upon substitution. self.assertEqual(self.m8.substitute(2), 0) @@ -193,7 +207,7 @@ def test_monomial_clone(self): # and same coefficient. self.assertEqual(self.m3, self.m3.clone()) - # The zero monomial is identified and + # The zero monomial is identified and # always clones to itself. self.assertEqual(self.m1, self.m8.clone()) self.assertEqual(self.m1, self.m1.clone()) @@ -203,4 +217,4 @@ def test_monomial_clone(self): if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/test_queues.py b/tests/test_queues.py index b5b49ef61..5a2f4c89d 100644 --- a/tests/test_queues.py +++ b/tests/test_queues.py @@ -80,13 +80,16 @@ def test_max_sliding_window(self): self.assertEqual(max_sliding_window(array, k=7), [6, 7]) array = [8, 5, 10, 7, 9, 4, 15, 12, 90, 13] - self.assertEqual(max_sliding_window(array, k=4), [10, 10, 10, 15, 15, 90, 90]) + self.assertEqual(max_sliding_window(array, k=4), + [10, 10, 10, 15, 15, 90, 90]) self.assertEqual(max_sliding_window(array, k=7), [15, 15, 90, 90]) - self.assertEqual(max_sliding_window(array, k=2), [8, 10, 10, 9, 9, 15, 15, 90, 90]) + self.assertEqual(max_sliding_window(array, k=2), + [8, 10, 10, 9, 9, 15, 15, 90, 90]) def test_reconstruct_queue(self): self.assertEqual([[5, 0], [7, 0], [5, 2], [6, 1], [4, 4], [7, 1]], - reconstruct_queue([[7, 0], [4, 4], [7, 1], [5, 0], [6, 1], [5, 2]])) + reconstruct_queue([[7, 0], [4, 4], [7, 1], [5, 0], + [6, 1], [5, 2]])) class TestPriorityQueue(unittest.TestCase): diff --git a/tests/test_search.py b/tests/test_search.py index d1ea8225d..8f9555048 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -42,7 +42,7 @@ def test_binary_search(self): self.assertEqual(11, binary_search_recur(array, 0, 11, 6)) self.assertEqual(-1, binary_search_recur(array, 0, 11, 7)) self.assertEqual(-1, binary_search_recur(array, 0, 11, -1)) - + def test_ternary_search(self): array = [1, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 6] self.assertEqual(10, ternary_search(0, 11, 5, array)) @@ -50,7 +50,6 @@ def test_ternary_search(self): self.assertEqual(-1, ternary_search(0, 10, 5, array)) self.assertEqual(-1, ternary_search(0, 11, 7, array)) self.assertEqual(-1, ternary_search(0, 11, -1, array)) - def test_last_occurrence(self): array = [1, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 6, 6, 6] diff --git a/tests/test_set.py b/tests/test_set.py index 37aea6f2a..e2985c942 100644 --- a/tests/test_set.py +++ b/tests/test_set.py @@ -4,7 +4,9 @@ import unittest + class TestFindKeyboardRow(unittest.TestCase): def test_find_keyboard_row(self): self.assertEqual(["Alaska", "Dad"], - find_keyboard_row(["Hello", "Alaska", "Dad", "Peace"])) + find_keyboard_row(["Hello", "Alaska", + "Dad", "Peace"])) diff --git a/tests/test_sort.py b/tests/test_sort.py index 2c23ab8a5..c80290fdf 100644 --- a/tests/test_sort.py +++ b/tests/test_sort.py @@ -7,7 +7,6 @@ cycle_sort, exchange_sort, max_heap_sort, min_heap_sort, - insertion_sort, merge_sort, pancake_sort, pigeonhole_sort, @@ -42,7 +41,8 @@ def test_bogo_sort(self): self.assertTrue(is_sorted(bogo_sort([1, 23, 5]))) def test_bitonic_sort(self): - self.assertTrue(is_sorted(bitonic_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) + self.assertTrue(is_sorted(bitonic_sort([1, 3, 2, 5, 65, + 23, 57, 1232]))) def test_bubble_sort(self): self.assertTrue(is_sorted(bubble_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) @@ -51,36 +51,43 @@ def test_comb_sort(self): self.assertTrue(is_sorted(comb_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_counting_sort(self): - self.assertTrue(is_sorted(counting_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) + self.assertTrue(is_sorted(counting_sort([1, 3, 2, 5, 65, + 23, 57, 1232]))) def test_cycle_sort(self): self.assertTrue(is_sorted(cycle_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_exchange_sort(self): - self.assertTrue(is_sorted(exchange_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) - + self.assertTrue(is_sorted(exchange_sort([1, 3, 2, 5, 65, + 23, 57, 1232]))) + def test_heap_sort(self): - self.assertTrue(is_sorted(max_heap_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) + self.assertTrue(is_sorted(max_heap_sort([1, 3, 2, 5, 65, + 23, 57, 1232]))) - self.assertTrue(is_sorted(min_heap_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) + self.assertTrue(is_sorted(min_heap_sort([1, 3, 2, 5, 65, + 23, 57, 1232]))) def test_insertion_sort(self): - self.assertTrue(is_sorted(bitonic_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) + self.assertTrue(is_sorted(bitonic_sort([1, 3, 2, 5, 65, + 23, 57, 1232]))) def test_merge_sort(self): self.assertTrue(is_sorted(merge_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_pancake_sort(self): - self.assertTrue(is_sorted(pancake_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) - + self.assertTrue(is_sorted(pancake_sort([1, 3, 2, 5, 65, + 23, 57, 1232]))) + def test_pigeonhole_sort(self): self.assertTrue(is_sorted(pigeonhole_sort([1, 5, 65, 23, 57, 1232]))) - + def test_quick_sort(self): self.assertTrue(is_sorted(quick_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_selection_sort(self): - self.assertTrue(is_sorted(selection_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) + self.assertTrue(is_sorted(selection_sort([1, 3, 2, 5, 65, + 23, 57, 1232]))) def test_bucket_sort(self): self.assertTrue(is_sorted(bucket_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) @@ -95,24 +102,25 @@ def test_gnome_sort(self): self.assertTrue(is_sorted(gnome_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) def test_cocktail_shaker_sort(self): - self.assertTrue(is_sorted(cocktail_shaker_sort([1, 3, 2, 5, 65, 23, 57, 1232]))) + self.assertTrue(is_sorted(cocktail_shaker_sort([1, 3, 2, 5, 65, + 23, 57, 1232]))) class TestTopSort(unittest.TestCase): def setUp(self): self.depGraph = { - "a" : [ "b" ], - "b" : [ "c" ], - "c" : [ 'e'], - 'e' : [ 'g' ], - "d" : [ ], - "f" : ["e" , "d"], - "g" : [ ] + "a": ["b"], + "b": ["c"], + "c": ['e'], + 'e': ['g'], + "d": [], + "f": ["e", "d"], + "g": [] } def test_topsort(self): res = top_sort_recursive(self.depGraph) - #print(res) + # print(res) self.assertTrue(res.index('g') < res.index('e')) res = top_sort(self.depGraph) self.assertTrue(res.index('g') < res.index('e')) diff --git a/tests/test_stack.py b/tests/test_stack.py index 78822bc17..93b389737 100644 --- a/tests/test_stack.py +++ b/tests/test_stack.py @@ -11,6 +11,8 @@ ) import unittest + + class TestSuite(unittest.TestCase): def test_is_consecutive(self): self.assertTrue(first_is_consecutive([3, 4, 5, 6, 7])) @@ -133,6 +135,7 @@ def test_LinkedListStack(self): self.assertTrue(stack.is_empty()) + class TestOrderedStack(unittest.TestCase): def test_OrderedStack(self): stack = OrderedStack() diff --git a/tests/test_streaming.py b/tests/test_streaming.py index 701b5f4ce..2f59827ef 100644 --- a/tests/test_streaming.py +++ b/tests/test_streaming.py @@ -9,22 +9,33 @@ class TestMisraGreis(unittest.TestCase): def test_misra_correct(self): - self.assertEqual({'4':5},misras_gries([1,4,4,4,5,4,4])) - self.assertEqual({'1':4},misras_gries([0,0,0,1,1,1,1])) - self.assertEqual({'0':4,'1':3},misras_gries([0,0,0,0,1,1,1,2,2],3)) - + self.assertEqual({'4': 5}, misras_gries([1, 4, 4, 4, 5, 4, 4])) + self.assertEqual({'1': 4}, misras_gries([0, 0, 0, 1, 1, 1, 1])) + self.assertEqual({'0': 4, '1': 3}, misras_gries([0, 0, 0, 0, 1, 1, + 1, 2, 2], 3)) + def test_misra_incorrect(self): - self.assertEqual(None,misras_gries([1,2,5,4,5,4,4,5,4,4,5])) - self.assertEqual(None,misras_gries([0,0,0,2,1,1,1])) - self.assertEqual(None,misras_gries([0,0,0,1,1,1])) + self.assertEqual(None, misras_gries([1, 2, 5, 4, 5, 4, 4, 5, 4, 4, 5])) + self.assertEqual(None, misras_gries([0, 0, 0, 2, 1, 1, 1])) + self.assertEqual(None, misras_gries([0, 0, 0, 1, 1, 1])) + class TestOneSparse(unittest.TestCase): def test_one_sparse_correct(self): - self.assertEqual(4,one_sparse([(4,'+'), (2,'+'),(2,'-'),(4,'+'),(3,'+'),(3,'-')])) - self.assertEqual(2,one_sparse([(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'+')])) - + self.assertEqual(4, one_sparse([(4, '+'), (2, '+'), (2, '-'), + (4, '+'), (3, '+'), (3, '-')])) + self.assertEqual(2, one_sparse([(2, '+'), (2, '+'), (2, '+'), + (2, '+'), (2, '+'), (2, '+'), + (2, '+')])) def test_one_sparse_incorrect(self): - self.assertEqual(None,one_sparse([(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'+'),(1,'+')])) #Two values remaining - self.assertEqual(None,one_sparse([(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'-'),(2,'-'),(2,'-'),(2,'-')])) # No values remaining - self.assertEqual(None,one_sparse([(2,'+'),(2,'+'),(4,'+'),(4,'+')])) # Bitsum sum of sign is inccorect + self.assertEqual(None, one_sparse([(2, '+'), (2, '+'), (2, '+'), + (2, '+'), (2, '+'), (2, '+'), + (1, '+')])) # Two values remaining + self.assertEqual(None, one_sparse([(2, '+'), (2, '+'), + (2, '+'), (2, '+'), + (2, '-'), (2, '-'), (2, '-'), + (2, '-')])) # No values remaining + # Bitsum sum of sign is inccorect + self.assertEqual(None, one_sparse([(2, '+'), (2, '+'), + (4, '+'), (4, '+')])) diff --git a/tests/test_strings.py b/tests/test_strings.py index 893498156..ee0d0d207 100644 --- a/tests/test_strings.py +++ b/tests/test_strings.py @@ -8,7 +8,7 @@ group_anagrams, int_to_roman, is_palindrome, is_palindrome_reverse, - is_palindrome_two_pointer, is_palindrome_stack, is_palindrome_deque, + is_palindrome_two_pointer, is_palindrome_stack, is_palindrome_deque, is_rotated, is_rotated_v1, license_number, make_sentence, @@ -20,9 +20,7 @@ reverse_vowel, reverse_words, roman_to_int, - strip_url_params1, strip_url_params2, strip_url_params3, - is_valid_coordinates_0, is_valid_coordinates_1, - is_valid_coordinates_regular_expression, + is_valid_coordinates_0, word_squares, convert_morse_word, unique_morse, judge_circle, @@ -34,7 +32,8 @@ repeat_string, text_justification, min_distance, - longest_common_prefix_v1, longest_common_prefix_v2, longest_common_prefix_v3, + longest_common_prefix_v1, longest_common_prefix_v2, + longest_common_prefix_v3, rotate, rotate_alt, first_unique_char, repeat_substring, @@ -79,12 +78,17 @@ def test_match_symbol(self): self.assertEqual(self.result, match_symbol(self.words, self.symbols)) def test_match_symbol_1(self): - self.assertEqual(['[Am]azon', 'Mi[cro]soft', 'Goog[le]'], match_symbol_1(self.words, self.symbols)) + self.assertEqual(['[Am]azon', 'Mi[cro]soft', 'Goog[le]'], + match_symbol_1(self.words, self.symbols)) def test_bracket(self): - self.assertEqual(('[Am]azon', 'Mi[cro]soft', 'Goog[le]'), bracket(self.words, self.symbols)) - self.assertEqual(('Amazon', 'Microsoft', 'Google'), bracket(self.words, ['thisshouldnotmatch'])) - self.assertEqual(('Amazon', 'M[i]crosoft', 'Google'), bracket(self.words, ['i', 'i'])) + self.assertEqual(('[Am]azon', 'Mi[cro]soft', 'Goog[le]'), + bracket(self.words, self.symbols)) + self.assertEqual(('Amazon', 'Microsoft', 'Google'), + bracket(self.words, ['thisshouldnotmatch'])) + self.assertEqual(('Amazon', 'M[i]crosoft', 'Google'), + bracket(self.words, ['i', 'i'])) + class TestDecodeString(unittest.TestCase): """[summary] @@ -120,7 +124,8 @@ class TestDomainExtractor(unittest.TestCase): """ def test_valid(self): - self.assertEqual(domain_name_1("https://github.com/SaadBenn"), "github") + self.assertEqual(domain_name_1("https://github.com/SaadBenn"), + "github") def test_invalid(self): self.assertEqual(domain_name_2("http://google.com"), "google") @@ -138,7 +143,8 @@ def test_encode(self): self.assertEqual("4:keon2:is7:awesome", encode("keon is awesome")) def test_decode(self): - self.assertEqual(['keon', 'is', 'awesome'], decode("4:keon2:is7:awesome")) + self.assertEqual(['keon', 'is', 'awesome'], + decode("4:keon2:is7:awesome")) class TestGroupAnagrams(unittest.TestCase): @@ -151,7 +157,8 @@ class TestGroupAnagrams(unittest.TestCase): def test_group_anagrams(self): self.assertEqual([['eat', 'tea', 'ate'], ['tan', 'nat'], ['bat']], \ - group_anagrams(["eat", "tea", "tan", "ate", "nat", "bat"])) + group_anagrams(["eat", "tea", "tan", "ate", "nat", + "bat"])) class TestIntToRoman(unittest.TestCase): @@ -231,7 +238,7 @@ def test_rotate(self): self.assertEqual("hello", rotate("hello", 5)) self.assertEqual("elloh", rotate("hello", 6)) self.assertEqual("llohe", rotate("hello", 7)) - + def test_rotate_alt(self): self.assertEqual("llohe", rotate_alt("hello", 2)) self.assertEqual("hello", rotate_alt("hello", 5)) @@ -400,14 +407,20 @@ def test_roman_to_int(self): # """ # def test_strip_url_params1(self): -# self.assertEqual(strip_url_params1("www.saadbenn.com?a=1&b=2&a=2"), "www.saadbenn.com?a=1&b=2") -# self.assertEqual(strip_url_params1("www.saadbenn.com?a=1&b=2", ['b']), "www.saadbenn.com?a=1") +# self.assertEqual(strip_url_params1("www.saadbenn.com?a=1&b=2&a=2"), +# "www.saadbenn.com?a=1&b=2") +# self.assertEqual(strip_url_params1("www.saadbenn.com?a=1&b=2", +# ['b']), "www.saadbenn.com?a=1") # def test_strip_url_params2(self): -# self.assertEqual(strip_url_params2("www.saadbenn.com?a=1&b=2&a=2"), "www.saadbenn.com?a=1&b=2") -# self.assertEqual(strip_url_params2("www.saadbenn.com?a=1&b=2", ['b']), "www.saadbenn.com?a=1") +# self.assertEqual(strip_url_params2("www.saadbenn.com?a=1&b=2&a=2"), +# "www.saadbenn.com?a=1&b=2") +# self.assertEqual(strip_url_params2("www.saadbenn.com?a=1&b=2", +# 'b']), "www.saadbenn.com?a=1") # def test_strip_url_params3(self): -# self.assertEqual(strip_url_params3("www.saadbenn.com?a=1&b=2&a=2"), "www.saadbenn.com?a=1&b=2") -# self.assertEqual(strip_url_params3("www.saadbenn.com?a=1&b=2", ['b']), "www.saadbenn.com?a=1") +# self.assertEqual(strip_url_params3("www.saadbenn.com?a=1&b=2&a=2"), +# "www.saadbenn.com?a=1&b=2") +# self.assertEqual(strip_url_params3("www.saadbenn.com?a=1&b=2", +# ['b']), "www.saadbenn.com?a=1") class TestValidateCoordinates(unittest.TestCase): @@ -424,7 +437,9 @@ def test_valid(self): self.assertTrue(is_valid_coordinates_0(coordinate)) def test_invalid(self): - invalid_coordinates = ["23.234, - 23.4234", "99.234, 12.324", "6.325624, 43.34345.345", "0, 1,2", "23.245, 1e1"] + invalid_coordinates = ["23.234, - 23.4234", "99.234, 12.324", + "6.325624, 43.34345.345", "0, 1,2", + "23.245, 1e1"] for coordinate in invalid_coordinates: self.assertFalse(is_valid_coordinates_0(coordinate)) @@ -438,8 +453,10 @@ class TestWordSquares(unittest.TestCase): """ def test_word_squares(self): - self.assertEqual([['wall', 'area', 'lead', 'lady'], ['ball', 'area', 'lead', 'lady']], \ - word_squares(["area", "lead", "wall", "lady", "ball"])) + self.assertEqual([['wall', 'area', 'lead', 'lady'], ['ball', 'area', + 'lead', 'lady']], \ + word_squares(["area", "lead", "wall", + "lady", "ball"])) class TestUniqueMorse(unittest.TestCase): @@ -468,6 +485,7 @@ def test_caesar_cipher(self): self.assertEqual("Lipps_Asvph!", caesar_cipher("Hello_World!", 4)) self.assertEqual("okffng-Qwvb", caesar_cipher("middle-Outz", 2)) + class TestCheckPangram(unittest.TestCase): def test_check_pangram(self): self.assertTrue(check_pangram("The quick brown fox jumps over the lazy dog")) @@ -499,47 +517,58 @@ def test_text_justification(self): self.assertEqual(["This is an", "example of text", "justification. "], - - text_justification(["This", "is", "an", "example", "of", "text", "justification."] - , 16) + text_justification(["This", "is", "an", "example", + "of", "text", + "justification."], 16) ) self.assertEqual(["What must be", "acknowledgment ", "shall be "], - - text_justification(["What", "must", "be", "acknowledgment", "shall", "be"] - , 16) + text_justification(["What", "must", "be", + "acknowledgment", "shall", + "be"], 16) ) + class TestMinDistance(unittest.TestCase): def test_min_distance(self): self.assertEqual(2, min_distance("sea", "eat")) self.assertEqual(6, min_distance("abAlgocrithmf", "Algorithmmd")) + class TestLongestCommonPrefix(unittest.TestCase): def test_longest_common_prefix(self): # Test first solution - self.assertEqual("fl", longest_common_prefix_v1(["flower","flow","flight"])) - self.assertEqual("", longest_common_prefix_v1(["dog","racecar","car"])) + self.assertEqual("fl", longest_common_prefix_v1(["flower", "flow", + "flight"])) + self.assertEqual("", longest_common_prefix_v1(["dog", "racecar", + "car"])) # Test second solution - self.assertEqual("fl", longest_common_prefix_v2(["flower","flow","flight"])) - self.assertEqual("", longest_common_prefix_v2(["dog","racecar","car"])) + self.assertEqual("fl", longest_common_prefix_v2(["flower", "flow", + "flight"])) + self.assertEqual("", longest_common_prefix_v2(["dog", "racecar", + "car"])) # Test third solution - self.assertEqual("fl", longest_common_prefix_v3(["flower","flow","flight"])) - self.assertEqual("", longest_common_prefix_v3(["dog","racecar","car"])) + self.assertEqual("fl", longest_common_prefix_v3(["flower", "flow", + "flight"])) + self.assertEqual("", longest_common_prefix_v3(["dog", "racecar", + "car"])) + class TestFirstUniqueChar(unittest.TestCase): def test_first_unique_char(self): self.assertEqual(0, first_unique_char("leetcode")) self.assertEqual(2, first_unique_char("loveleetcode")) + class TestRepeatSubstring(unittest.TestCase): def test_repeat_substring(self): self.assertTrue(repeat_substring("abab")) self.assertFalse(repeat_substring("aba")) self.assertTrue(repeat_substring("abcabcabcabc")) + class TestAtbashCipher(unittest.TestCase): """[summary] Test for the file atbash_cipher.py @@ -555,22 +584,19 @@ def test_atbash_cipher(self): self.assertEqual("ZggzXP zg WzdM", atbash("AttaCK at DawN")) - class TestLongestPalindromicSubstring(unittest.TestCase): """[summary] Test for the file longest_palindromic_substring.py - Arguments: unittest {[type]} -- [description] """ - def test_longest_palindromic_substring(self): self.assertEqual("bb", longest_palindrome("cbbd")) self.assertEqual("abba", longest_palindrome("abba")) self.assertEqual("asdadsa", longest_palindrome("dasdasdasdasdasdadsa")) self.assertEqual("abba", longest_palindrome("cabba")) - + class TestKnuthMorrisPratt(unittest.TestCase): """[summary] Test for the file knuth_morris_pratt.py @@ -585,6 +611,7 @@ def test_knuth_morris_pratt(self): self.assertEqual([0, 4], knuth_morris_pratt("abcdabc", "abc")) self.assertEqual([], knuth_morris_pratt("aabcdaab", "aba")) + class TestPanagram(unittest.TestCase): """[summary] Test for the file panagram.py @@ -663,33 +690,34 @@ def test_swedish_panagram(self): # Assert self.assertEqual(True, res) + class TestFizzbuzz(unittest.TestCase): """[summary] Tests for the fizzbuzz method in file fizzbuzz.py """ - def test_fizzbuzz(self): - # Testing that n < 0 returns a Value Error - self.assertRaises(ValueError, fizzbuzz.fizzbuzz, -2) - - # Testing that a string returns a Type Error. - self.assertRaises(TypeError, fizzbuzz.fizzbuzz, "hello") - - # Testing a base case, n = 3 - result = fizzbuzz.fizzbuzz(3) - expected = [1, 2, "Fizz"] - self.assertEqual(result, expected) - - # Testing a base case, n = 5 - result = fizzbuzz.fizzbuzz(5) - expected = [1, 2, "Fizz", 4, "Buzz"] - self.assertEqual(result, expected) - - # Testing a base case, n = 15 i.e. mod 3 and 5 - result = fizzbuzz.fizzbuzz(15) - expected = [1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz", 11, - "Fizz", 13, 14, "FizzBuzz"] - self.assertEqual(result, expected) - + # Testing that n < 0 returns a Value Error + self.assertRaises(ValueError, fizzbuzz.fizzbuzz, -2) + + # Testing that a string returns a Type Error. + self.assertRaises(TypeError, fizzbuzz.fizzbuzz, "hello") + + # Testing a base case, n = 3 + result = fizzbuzz.fizzbuzz(3) + expected = [1, 2, "Fizz"] + self.assertEqual(result, expected) + + # Testing a base case, n = 5 + result = fizzbuzz.fizzbuzz(5) + expected = [1, 2, "Fizz", 4, "Buzz"] + self.assertEqual(result, expected) + + # Testing a base case, n = 15 i.e. mod 3 and 5 + result = fizzbuzz.fizzbuzz(15) + expected = [1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz", 11, + "Fizz", 13, 14, "FizzBuzz"] + self.assertEqual(result, expected) + + if __name__ == "__main__": unittest.main() diff --git a/tests/test_tree.py b/tests/test_tree.py index 170a931dd..c9d0f0fd4 100644 --- a/tests/test_tree.py +++ b/tests/test_tree.py @@ -65,7 +65,8 @@ def setUpClass(cls): cls.range = 10000 def setUp(self): - self.keys_to_insert = [self.random.randrange(-self.range, self.range) for i in range(self.range)] + self.keys_to_insert = [self.random.randrange(-self.range, self.range) + for i in range(self.range)] def test_insertion_and_find_even_degree(self): btree = BTree(4) @@ -111,16 +112,18 @@ def test_deletion_odd_degree(self): self.assertEqual(btree.root.keys, []) self.assertEqual(btree.root.children, []) + class TestConstructTreePreorderPostorder(unittest.TestCase): def test_construct_tree(self): - + # Test 1 ctpp.pre_index = 0 pre1 = [1, 2, 4, 8, 9, 5, 3, 6, 7] post1 = [8, 9, 4, 5, 2, 6, 7, 3, 1] size1 = len(pre1) - self.assertEqual(ctpp.construct_tree(pre1, post1, size1), [8,4,9,2,5,1,6,3,7]) + self.assertEqual(ctpp.construct_tree(pre1, post1, size1), + [8, 4, 9, 2, 5, 1, 6, 3, 7]) # Test 2 ctpp.pre_index = 0 @@ -128,7 +131,8 @@ def test_construct_tree(self): post2 = [4, 5, 2, 6, 7, 3, 1] size2 = len(pre2) - self.assertEqual(ctpp.construct_tree(pre2, post2, size2), [4,2,5,1,6,3,7]) + self.assertEqual(ctpp.construct_tree(pre2, post2, size2), + [4, 2, 5, 1, 6, 3, 7]) # Test 3 ctpp.pre_index = 0 @@ -136,7 +140,8 @@ def test_construct_tree(self): post3 = [16, 21, 7, 1, 9, 5, 12] size3 = len(pre3) - self.assertEqual(ctpp.construct_tree(pre3, post3, size3), [16,7,21,12,1,5,9]) + self.assertEqual(ctpp.construct_tree(pre3, post3, size3), + [16, 7, 21, 12, 1, 5, 9]) class TestFenwickTree(unittest.TestCase): @@ -170,5 +175,6 @@ def test_construct_tree_with_update_3(self): ft.update_bit(bit_tree, 2, 11) self.assertEqual(23, ft.get_sum(bit_tree, 4)) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_unix.py b/tests/test_unix.py index e7a6c6349..3cafba98f 100644 --- a/tests/test_unix.py +++ b/tests/test_unix.py @@ -6,12 +6,18 @@ ) import os import unittest + + class TestUnixPath(unittest.TestCase): def test_join_with_slash(self): - self.assertEqual("path/to/dir/file", join_with_slash("path/to/dir/", "file")) - self.assertEqual("path/to/dir/file", join_with_slash("path/to/dir", "file")) - self.assertEqual("http://algorithms/part", join_with_slash("http://algorithms", "part")) - self.assertEqual("http://algorithms/part", join_with_slash("http://algorithms/", "part")) + self.assertEqual("path/to/dir/file", + join_with_slash("path/to/dir/", "file")) + self.assertEqual("path/to/dir/file", + join_with_slash("path/to/dir", "file")) + self.assertEqual("http://algorithms/part", + join_with_slash("http://algorithms", "part")) + self.assertEqual("http://algorithms/part", + join_with_slash("http://algorithms/", "part")) def test_full_path(self): file_name = "file_name" From 8149be50969a3ef587d938f029c5bc56169493bf Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Wed, 2 Mar 2022 05:14:02 +0100 Subject: [PATCH 077/101] [Documentation] knuth_morris_pratt (#835) * attach documentation to method; add annotations * add test case for list of integers Co-authored-by: Pieter Eendebak --- algorithms/strings/knuth_morris_pratt.py | 28 ++++++++++++++++-------- tests/test_strings.py | 1 + 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/algorithms/strings/knuth_morris_pratt.py b/algorithms/strings/knuth_morris_pratt.py index 0bdf4d550..e1953a218 100644 --- a/algorithms/strings/knuth_morris_pratt.py +++ b/algorithms/strings/knuth_morris_pratt.py @@ -1,13 +1,23 @@ -""" -Given two strings text and pattern, -return the list of start indexes in text that matches with the pattern -using knuth_morris_pratt algorithm. -If idx is in the list, text[idx : idx + M] matches with pattern. -Time complexity : O(N+M) -N and M is the length of text and pattern, respectively. -""" +from typing import Sequence, List -def knuth_morris_pratt(text, pattern): +def knuth_morris_pratt(text : Sequence, pattern : Sequence) -> List[int]: + """ + Given two strings text and pattern, return the list of start indexes in text that matches with the pattern + using knuth_morris_pratt algorithm. + + Args: + text: Text to search + pattern: Pattern to search in the text + Returns: + List of indices of patterns found + + Example: + >>> knuth_morris_pratt('hello there hero!', 'he') + [0, 7, 12] + + If idx is in the list, text[idx : idx + M] matches with pattern. + Time complexity of the algorithm is O(N+M), with N and M the length of text and pattern, respectively. + """ n = len(text) m = len(pattern) pi = [0 for i in range(m)] diff --git a/tests/test_strings.py b/tests/test_strings.py index ee0d0d207..57f5ff0c1 100644 --- a/tests/test_strings.py +++ b/tests/test_strings.py @@ -610,6 +610,7 @@ def test_knuth_morris_pratt(self): self.assertEqual([0, 1, 2, 3, 4], knuth_morris_pratt("aaaaaaa", "aaa")) self.assertEqual([0, 4], knuth_morris_pratt("abcdabc", "abc")) self.assertEqual([], knuth_morris_pratt("aabcdaab", "aba")) + self.assertEqual([0, 4], knuth_morris_pratt([0,0,1,1,0,0,1,0], [0,0])) class TestPanagram(unittest.TestCase): From 15e5292d21f1fde70a4cdb428c25db0192b5b7f0 Mon Sep 17 00:00:00 2001 From: Ankit Agarwal Date: Wed, 2 Mar 2022 17:48:34 +0530 Subject: [PATCH 078/101] Fix flake8 issues (#837) --- algorithms/compression/elias.py | 26 +++-- algorithms/dp/coin_change.py | 21 ++-- algorithms/dp/edit_distance.py | 31 +++-- algorithms/dp/egg_drop.py | 33 +++--- algorithms/dp/fib.py | 21 ++-- algorithms/dp/hosoya_triangle.py | 42 +++---- algorithms/dp/job_scheduling.py | 39 ++++--- algorithms/dp/k_factor.py | 108 ++++++++++-------- algorithms/dp/longest_increasing.py | 53 +++++---- algorithms/dp/matrix_chain_order.py | 39 ++++--- algorithms/dp/min_cost_path.py | 34 +++--- algorithms/dp/planting_trees.py | 22 ++-- .../path_between_two_vertices_in_digraph.py | 41 +++---- .../maths/diffie_hellman_key_exchange.py | 45 ++++---- algorithms/maths/polynomial.py | 8 +- .../maths/symmetry_group_cycle_index.py | 108 +++++++++--------- algorithms/matrix/bomb_enemy.py | 25 ++-- .../matrix/cholesky_matrix_decomposition.py | 9 +- algorithms/matrix/copy_transform.py | 4 + .../matrix/crout_matrix_decomposition.py | 3 +- algorithms/matrix/matrix_exponentiation.py | 6 +- algorithms/matrix/rotate_image.py | 6 +- algorithms/matrix/search_in_sorted_matrix.py | 6 +- algorithms/matrix/sort_matrix_diagonally.py | 2 +- algorithms/matrix/sparse_dot_vector.py | 3 +- algorithms/matrix/sparse_mul.py | 25 ++-- algorithms/matrix/sudoku_validator.py | 15 ++- algorithms/matrix/sum_sub_squares.py | 19 ++- algorithms/queues/max_sliding_window.py | 2 + algorithms/queues/priority_queue.py | 1 - algorithms/queues/queue.py | 3 + algorithms/queues/zigzagiterator.py | 13 ++- algorithms/set/find_keyboard_row.py | 1 + algorithms/stack/is_consecutive.py | 10 +- algorithms/stack/is_sorted.py | 2 + algorithms/stack/longest_abs_path.py | 66 +++++------ algorithms/stack/ordered_stack.py | 65 ++++++----- algorithms/stack/remove_min.py | 2 + algorithms/stack/simplify_path.py | 5 +- algorithms/stack/stack.py | 2 + algorithms/stack/stutter.py | 2 + algorithms/stack/switch_pairs.py | 8 +- algorithms/stack/valid_parenthesis.py | 2 + tests/test_iterative_segment_tree.py | 2 +- 44 files changed, 548 insertions(+), 432 deletions(-) diff --git a/algorithms/compression/elias.py b/algorithms/compression/elias.py index bdeb7fd39..974fc3772 100644 --- a/algorithms/compression/elias.py +++ b/algorithms/compression/elias.py @@ -1,17 +1,20 @@ """ -Elias γ code or Elias gamma code is a universal code encoding positive integers. -It is used most commonly when coding integers whose upper-bound cannot be determined beforehand. -Elias δ code or Elias delta code is a universal code encoding the positive integers, +Elias γ code or Elias gamma code is a universal code +encoding positive integers. +It is used most commonly when coding integers whose +upper-bound cannot be determined beforehand. +Elias δ code or Elias delta code is a universal code + encoding the positive integers, that includes Elias γ code when calculating. Both were developed by Peter Elias. """ -from math import log,ceil +from math import log -log2 = lambda x: log(x,2) +log2 = lambda x: log(x, 2) # Calculates the binary number -def binary(x,l=1): +def binary(x, l=1): fmt = '{0:0%db}' % l return fmt.format(x) @@ -21,12 +24,13 @@ def unary(x): def elias_generic(lencoding, x): """ - The compressed data is calculated in two parts. - The first part is the unary number of 1 + ⌊log2(x)⌋. + The compressed data is calculated in two parts. + The first part is the unary number of 1 + ⌊log2(x)⌋. The second part is the binary number of x - 2^(⌊log2(x)⌋). For the final result we add these two parts. """ - if x == 0: return '0' + if x == 0: + return '0' first_part = 1 + int(log2(x)) @@ -34,7 +38,7 @@ def elias_generic(lencoding, x): k = int(log2(x)) - return lencoding(first_part) + binary(a,k) + return lencoding(first_part) + binary(a, k) def elias_gamma(x): """ @@ -46,4 +50,4 @@ def elias_delta(x): """ For the first part we put the elias_g of the number. """ - return elias_generic(elias_gamma,x) + return elias_generic(elias_gamma, x) diff --git a/algorithms/dp/coin_change.py b/algorithms/dp/coin_change.py index e9a83fcbf..a1d739d9b 100644 --- a/algorithms/dp/coin_change.py +++ b/algorithms/dp/coin_change.py @@ -1,28 +1,31 @@ """ Problem -Given a value n, if we want to make change for N cents, and we have infinite supply of each of -coins = {S1, S2, .. , Sm} valued coins, how many ways can we make the change? +Given a value n, if we want to make change for N cents, +and we have infinite supply of each of +coins = {S1, S2, .. , Sm} valued coins, how many ways +can we make the change? The order of coins doesn't matter. -For example, for n = 4 and coins = [1, 2, 3], there are four solutions: -[1, 1, 1, 1], [1, 1, 2], [2, 2], [1, 3]. -So output should be 4. +For example, for n = 4 and coins = [1, 2, 3], there are +four solutions: +[1, 1, 1, 1], [1, 1, 2], [2, 2], [1, 3]. +So output should be 4. -For n = 10 and coins = [2, 5, 3, 6], there are five solutions: -[2, 2, 2, 2, 2], [2, 2, 3, 3], [2, 2, 6], [2, 3, 5] and [5, 5]. +For n = 10 and coins = [2, 5, 3, 6], there are five solutions: +[2, 2, 2, 2, 2], [2, 2, 3, 3], [2, 2, 6], [2, 3, 5] and [5, 5]. So the output should be 5. Time complexity: O(n * m) where n is the value and m is the number of coins Space complexity: O(n) """ + def count(coins, n): # initialize dp array and set base case as 1 dp = [1] + [0] * n - + # fill dp in a bottom up manner for coin in coins: for i in range(coin, n+1): dp[i] += dp[i-coin] return dp[n] - diff --git a/algorithms/dp/edit_distance.py b/algorithms/dp/edit_distance.py index ecf6beb06..c21c766b7 100644 --- a/algorithms/dp/edit_distance.py +++ b/algorithms/dp/edit_distance.py @@ -1,34 +1,43 @@ -"""The edit distance between two words is the minimum number of letter insertions, -letter deletions, and letter substitutions required to transform one word into another. +"""The edit distance between two words is the minimum number +of letter insertions, letter deletions, and letter substitutions +required to transform one word into another. -For example, the edit distance between FOOD and MONEY is at most four: +For example, the edit distance between FOOD and MONEY is at +most four: FOOD -> MOOD -> MOND -> MONED -> MONEY -Given two words A and B, find the minimum number of operations required to transform one string into the other. +Given two words A and B, find the minimum number of operations +required to transform one string into the other. In other words, find the edit distance between A and B. Thought process: -Let edit(i, j) denote the edit distance between the prefixes A[1..i] and B[1..j]. +Let edit(i, j) denote the edit distance between +the prefixes A[1..i] and B[1..j]. Then, the function satifies the following recurrence: edit(i, j) = i if j = 0 j if i = 0 - min(edit(i-1, j) + 1, + min(edit(i-1, j) + 1, edit(i, j-1), + 1, edit(i-1, j-1) + cost) otherwise -There are two base cases, both of which occur when one string is empty and the other is not. -1. To convert an empty string A into a string B of length n, perform n insertions. -2. To convert a string A of length m into an empty string B, perform m deletions. +There are two base cases, both of which occur when one string is empty +and the other is not. +1. To convert an empty string A into a string B of length n, +perform n insertions. +2. To convert a string A of length m into an empty string B, +perform m deletions. Here, the cost is 1 if a substitution is required, -or 0 if both chars in words A and B are the same at indexes i and j, respectively. +or 0 if both chars in words A and B are the same at +indexes i and j, respectively. To find the edit distance between two words A and B, -we need to find edit(m, n), where m is the length of A and n is the length of B. +we need to find edit(m, n), where m is the length of A and n +is the length of B. """ diff --git a/algorithms/dp/egg_drop.py b/algorithms/dp/egg_drop.py index d07bee872..b9d38508a 100644 --- a/algorithms/dp/egg_drop.py +++ b/algorithms/dp/egg_drop.py @@ -1,21 +1,21 @@ """ -You are given K eggs, and you have access to a building with N floors -from 1 to N. Each egg is identical in function, and if an egg breaks, -you cannot drop it again. You know that there exists a floor F with -0 <= F <= N such that any egg dropped at a floor higher than F will -break, and any egg dropped at or below floor F will not break. -Each move, you may take an egg (if you have an unbroken one) and drop -it from any floor X (with 1 <= X <= N). Your goal is to know with -certainty what the value of F is. What is the minimum number of moves -that you need to know with certainty what F is, regardless of the +You are given K eggs, and you have access to a building with N floors +from 1 to N. Each egg is identical in function, and if an egg breaks, +you cannot drop it again. You know that there exists a floor F with +0 <= F <= N such that any egg dropped at a floor higher than F will +break, and any egg dropped at or below floor F will not break. +Each move, you may take an egg (if you have an unbroken one) and drop +it from any floor X (with 1 <= X <= N). Your goal is to know with +certainty what the value of F is. What is the minimum number of moves +that you need to know with certainty what F is, regardless of the initial value of F? Example: Input: K = 1, N = 2 Output: 2 -Explanation: +Explanation: Drop the egg from floor 1. If it breaks, we know with certainty that F = 0. -Otherwise, drop the egg from floor 2. If it breaks, we know with +Otherwise, drop the egg from floor 2. If it breaks, we know with certainty that F = 1. If it didn't break, then we know with certainty F = 2. Hence, we needed 2 moves in the worst case to know what F is with certainty. @@ -24,20 +24,21 @@ # A Dynamic Programming based Python Program for the Egg Dropping Puzzle INT_MAX = 32767 + def egg_drop(n, k): # A 2D table where entery eggFloor[i][j] will represent minimum # number of trials needed for i eggs and j floors. - egg_floor = [[0 for x in range(k+1)] for x in range(n+1)] - + egg_floor = [[0 for x in range(k+1)] for x in range(n + 1)] + # We need one trial for one floor and 0 trials for 0 floors for i in range(1, n+1): egg_floor[i][1] = 1 egg_floor[i][0] = 0 - + # We always need j trials for one egg and j floors. for j in range(1, k+1): egg_floor[1][j] = j - + # Fill rest of the entries in table using optimal substructure # property for i in range(2, n+1): @@ -47,6 +48,6 @@ def egg_drop(n, k): res = 1 + max(egg_floor[i-1][x-1], egg_floor[i][j-x]) if res < egg_floor[i][j]: egg_floor[i][j] = res - + # eggFloor[n][k] holds the result return egg_floor[n][k] diff --git a/algorithms/dp/fib.py b/algorithms/dp/fib.py index 7a6fd7140..f51020b74 100644 --- a/algorithms/dp/fib.py +++ b/algorithms/dp/fib.py @@ -1,6 +1,8 @@ ''' -In mathematics, the Fibonacci numbers, commonly denoted Fn, form a sequence, called the Fibonacci sequence, -such that each number is the sum of the two preceding ones, starting from 0 and 1. +In mathematics, the Fibonacci numbers, commonly denoted Fn, +form a sequence, called the Fibonacci sequence, +such that each number is the sum of the two preceding ones, +starting from 0 and 1. That is, F0=0 , F1=1 and @@ -8,10 +10,13 @@ The Fibonacci numbers are the numbers in the following integer sequence. 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ……. -In mathematical terms, the sequence Fn of Fibonacci numbers is defined by the recurrence relation +In mathematical terms, the sequence Fn of Fibonacci numbers is +defined by the recurrence relation Here, given a number n, print n-th Fibonacci Number. ''' + + def fib_recursive(n): """[summary] Computes the n-th fibonacci number recursive. @@ -20,7 +25,7 @@ def fib_recursive(n): Arguments: n {[int]} -- [description] - + Returns: [int] -- [description] """ @@ -35,15 +40,16 @@ def fib_recursive(n): # print(fib_recursive(35)) # => 9227465 (slow) + def fib_list(n): """[summary] This algorithm computes the n-th fibbonacci number very quick. approximate O(n) The algorithm use dynamic programming. - + Arguments: n {[int]} -- [description] - + Returns: [int] -- [description] """ @@ -58,13 +64,14 @@ def fib_list(n): # print(fib_list(100)) # => 354224848179261915075 + def fib_iter(n): """[summary] Works iterative approximate O(n) Arguments: n {[int]} -- [description] - + Returns: [int] -- [description] """ diff --git a/algorithms/dp/hosoya_triangle.py b/algorithms/dp/hosoya_triangle.py index 9bf9437ec..d2e1a8587 100644 --- a/algorithms/dp/hosoya_triangle.py +++ b/algorithms/dp/hosoya_triangle.py @@ -8,11 +8,11 @@ For example: printHosoya( 6 ) would return: -1 -1 1 -2 1 2 -3 2 2 3 -5 3 4 3 5 +1 +1 1 +2 1 2 +3 2 2 3 +5 3 4 3 5 8 5 6 6 5 8 The complexity is O(n^3). @@ -20,26 +20,28 @@ """ -def hosoya(n, m): +def hosoya(n, m): if ((n == 0 and m == 0) or (n == 1 and m == 0) or - (n == 1 and m == 1) or (n == 2 and m == 1)): + (n == 1 and m == 1) or (n == 2 and m == 1)): return 1 - if n > m: - return hosoya(n - 1, m) + hosoya(n - 2, m) - elif m == n: - return hosoya(n - 1, m - 1) + hosoya(n - 2, m - 2) - else: + if n > m: + return hosoya(n - 1, m) + hosoya(n - 2, m) + elif m == n: + return hosoya(n - 1, m - 1) + hosoya(n - 2, m - 2) + else: return 0 - -def print_hosoya(n): - for i in range(n): - for j in range(i + 1): - print(hosoya(i, j) , end = " ") - print ("\n", end = "") + + +def print_hosoya(n): + for i in range(n): + for j in range(i + 1): + print(hosoya(i, j), end=" ") + print("\n", end="") + def hosoya_testing(n): x = [] - for i in range(n): - for j in range(i + 1): + for i in range(n): + for j in range(i + 1): x.append(hosoya(i, j)) return x diff --git a/algorithms/dp/job_scheduling.py b/algorithms/dp/job_scheduling.py index eaf98ec44..f1a92f584 100644 --- a/algorithms/dp/job_scheduling.py +++ b/algorithms/dp/job_scheduling.py @@ -1,14 +1,16 @@ -# Python program for weighted job scheduling using Dynamic +# Python program for weighted job scheduling using Dynamic # Programming and Binary Search - + # Class to represent a job + + class Job: def __init__(self, start, finish, profit): - self.start = start + self.start = start self.finish = finish - self.profit = profit - - + self.profit = profit + + # A Binary Search based function to find the latest job # (before current job) that doesn't conflict with current # job. "index" is index of the current job. This function @@ -16,11 +18,11 @@ def __init__(self, start, finish, profit): # The array jobs[] is sorted in increasing order of finish # time. def binary_search(job, start_index): - + # Initialize 'lo' and 'hi' for Binary Search lo = 0 hi = start_index - 1 - + # Perform binary Search iteratively while lo <= hi: mid = (lo + hi) // 2 @@ -32,31 +34,32 @@ def binary_search(job, start_index): else: hi = mid - 1 return -1 - + + # The main function that returns the maximum possible # profit from given array of jobs def schedule(job): - + # Sort jobs according to finish time - job = sorted(job, key = lambda j: j.finish) - + job = sorted(job, key=lambda j: j.finish) + # Create an array to store solutions of subproblems. table[i] # stores the profit for jobs till arr[i] (including arr[i]) - n = len(job) + n = len(job) table = [0 for _ in range(n)] - + table[0] = job[0].profit - + # Fill entries in table[] using recursive property for i in range(1, n): - + # Find profit including the current job incl_prof = job[i].profit l = binary_search(job, i) if (l != -1): incl_prof += table[l] - + # Store maximum of including and excluding table[i] = max(incl_prof, table[i - 1]) - + return table[n-1] diff --git a/algorithms/dp/k_factor.py b/algorithms/dp/k_factor.py index 2fd23d18b..998d825ee 100644 --- a/algorithms/dp/k_factor.py +++ b/algorithms/dp/k_factor.py @@ -1,14 +1,24 @@ -'''The K factor of a string is defined as the number of times 'abba' appears as a substring. -Given two numbers N and k,​ find the number of strings of length N with 'K factor' = k. +'''The K factor of a string is defined as the number of +times 'abba' appears as a substring. + +Given two numbers N and k, find the number of strings of +length N with 'K factor' = k. The algorithms is as follows: -dp[n][k] will be a 4 element array, wherein each element can be the number of strings of length n and 'K factor' = k which belong to the criteria represented by that index: +dp[n][k] will be a 4 element array, wherein each element can be +the number of strings of length n and 'K factor' = k which +belong to the criteria represented by that index: - dp[n][k][0] can be the number of strings of length n and K-factor = k which end with substring 'a' - dp[n][k][1] can be the number of strings of length n and K-factor = k which end with substring 'ab' - dp[n][k][2] can be the number of strings of length n and K-factor = k which end with substring 'abb' - dp[n][k][3] can be the number of strings of length n and K-factor = k which end with anything other than the above substrings (anything other than 'a' 'ab' 'abb') + dp[n][k][0] can be the number of strings of length n and K-factor = k + which end with substring 'a' + dp[n][k][1] can be the number of strings of length n and K-factor = k + which end with substring 'ab' + dp[n][k][2] can be the number of strings of length n and K-factor = k + which end with substring 'abb' + dp[n][k][3] can be the number of strings of length n and K-factor = k + which end with anything other than the above substrings (anything other + than 'a' 'ab' 'abb') Example inputs @@ -18,51 +28,51 @@ ''' -def find_k_factor(n,k): - dp=[[[0 for i in range(4)]for j in range((n-1)//3+2)]for k in range(n+1)] - if(3*k+1>n): + +def find_k_factor(n, k): + dp = [[[0 for i in range(4)]for j in range((n-1)//3+2)]for k in range(n+1)] + if(3*k+1 > n): return 0 - #base cases - dp[1][0][0]=1; - dp[1][0][1]=0; - dp[1][0][2]=0; - dp[1][0][3]=25; + # base cases + dp[1][0][0] = 1 + dp[1][0][1] = 0 + dp[1][0][2] = 0 + dp[1][0][3] = 25 - for i in range(2,n+1): + for i in range(2, n+1): for j in range((n-1)//3+2): - if(j==0): - #adding a at the end - dp[i][j][0]=dp[i-1][j][0]+dp[i-1][j][1]+dp[i-1][j][3] - - #adding b at the end - dp[i][j][1]=dp[i-1][j][0] - dp[i][j][2]=dp[i-1][j][1] - - #adding any other lowercase character - dp[i][j][3]=dp[i-1][j][0]*24+dp[i-1][j][1]*24+dp[i-1][j][2]*25+dp[i-1][j][3]*25 - - elif(3*j+1>1 + mid = (l+r) >> 1 if i <= mid: - update(p<<1, l, mid, i, v) + update(p << 1, l, mid, i, v) else: - update((p<<1)|1, mid+1, r, i, v) - tree[p] = max(tree[p<<1], tree[(p<<1)|1]) + update((p << 1) | 1, mid + 1, r, i, v) + tree[p] = max(tree[p << 1], tree[(p << 1) | 1]) def get_max(p, l, r, s, e): if l > e or r < s: return 0 if l >= s and r <= e: return tree[p] - mid = (l+r)>>1 - return max(get_max(p<<1, l, mid, s, e), get_max((p<<1)|1, mid+1, r, s, e)) + mid = (l+r) >> 1 + return max(get_max(p << 1, l, mid, s, e), get_max((p << 1) | 1, mid+1, r, s, e)) ans = 0 for x in sequence: - cur = get_max(1, 0, mx, 0, x-1)+1 - ans = max(ans, cur) - update(1, 0, mx, x, cur) + cur = get_max(1, 0, mx, 0, x-1)+1 + ans = max(ans, cur) + update(1, 0, mx, x, cur) return ans + def longest_increasing_subsequence_optimized2(sequence): """ Optimized dynamic programming algorithm for @@ -81,31 +86,31 @@ def longest_increasing_subsequence_optimized2(sequence): rtype: int """ n = len(sequence) - tree = [0] * (n<<2) + tree = [0] * (n << 2) sorted_seq = sorted((x, -i) for i, x in enumerate(sequence)) + def update(p, l, r, i, v): - if l ==r: + if l == r: tree[p] = v return - mid = (l+r)>>1 + mid = (l+r) >> 1 if i <= mid: - update(p<<1, l, mid, i, v) + update(p << 1, l, mid, i, v) else: - update((p<<1)|1, mid+1, r, i, v) - tree[p] = max(tree[p<<1], tree[(p<<1)|1]) + update((p << 1) | 1, mid+1, r, i, v) + tree[p] = max(tree[p << 1], tree[(p << 1) | 1]) def get_max(p, l, r, s, e): if l > e or r < s: return 0 if l >= s and r <= e: return tree[p] - mid = (l+r)>>1 - return max(get_max(p<<1, l, mid, s, e), get_max((p<<1)|1, mid+1, r, s, e)) + mid = (l+r) >> 1 + return max(get_max(p << 1, l, mid, s, e), get_max((p << 1) | 1, mid+1, r, s, e)) ans = 0 for x, j in sorted_seq: i = -j cur = get_max(1, 0, n-1, 0, i-1)+1 ans = max(ans, cur) update(1, 0, n-1, i, cur) - return ans - + return ans diff --git a/algorithms/dp/matrix_chain_order.py b/algorithms/dp/matrix_chain_order.py index 8c1ecae43..47386f66f 100644 --- a/algorithms/dp/matrix_chain_order.py +++ b/algorithms/dp/matrix_chain_order.py @@ -6,12 +6,13 @@ ''' INF = float("inf") + def matrix_chain_order(array): - n=len(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 chain_length in range(2,n): - for a in range(1,n-chain_length+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] = INF @@ -20,26 +21,30 @@ def matrix_chain_order(array): if cost < matrix[a][b]: matrix[a][b] = cost sol[a][b] = c - return matrix , sol -#Print order of matrix with Ai as matrix + return matrix, sol +# Print order of matrix with Ai as matrix + -def print_optimal_solution(optimal_solution,i,j): - if i==j: - print("A" + str(i),end = " ") +def print_optimal_solution(optimal_solution, i, j): + if i == j: + print("A" + str(i), end=" ") else: - print("(",end = " ") - print_optimal_solution(optimal_solution,i,optimal_solution[i][j]) - print_optimal_solution(optimal_solution,optimal_solution[i][j]+1,j) - print(")",end = " ") + print("(", end=" ") + print_optimal_solution(optimal_solution, i, optimal_solution[i][j]) + print_optimal_solution(optimal_solution, optimal_solution[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 , optimal_solution = matrix_chain_order(array) + matrix, optimal_solution = matrix_chain_order(array) print("No. of Operation required: "+str((matrix[1][n-1]))) - print_optimal_solution(optimal_solution,1,n-1) + print_optimal_solution(optimal_solution, 1, n-1) + + if __name__ == '__main__': main() diff --git a/algorithms/dp/min_cost_path.py b/algorithms/dp/min_cost_path.py index 8af947075..356489e9a 100644 --- a/algorithms/dp/min_cost_path.py +++ b/algorithms/dp/min_cost_path.py @@ -1,7 +1,7 @@ """ author @goswami-rahul -To find minimum cost path +To find minimum cost path from station 0 to station N-1, where cost of moving from ith station to jth station is given as: @@ -9,11 +9,11 @@ where Matrix[i][j] denotes the cost of moving from station i --> station j for i < j -NOTE that values where Matrix[i][j] and i > j does not +NOTE that values where Matrix[i][j] and i > j does not mean anything, and hence represented by -1 or INF -For the input below (cost matrix), -Minimum cost is obtained as from { 0 --> 1 --> 3} +For the input below (cost matrix), +Minimum cost is obtained as from { 0 --> 1 --> 3} = cost[0][1] + cost[1][3] = 65 the Output will be: @@ -21,33 +21,35 @@ Time Complexity: O(n^2) Space Complexity: O(n) -""" +""" INF = float("inf") + def min_cost(cost): - + n = len(cost) # dist[i] stores minimum cost from 0 --> i. dist = [INF] * n dist[0] = 0 # cost from 0 --> 0 is zero. - + for i in range(n): - for j in range(i+1,n): + for j in range(i+1, n): dist[j] = min(dist[j], dist[i] + cost[i][j]) - + return dist[n-1] + if __name__ == '__main__': - - cost = [ [ 0, 15, 80, 90], # cost[i][j] is the cost of - [-1, 0, 40, 50], # going from i --> j - [-1, -1, 0, 70], - [-1, -1, -1, 0] ] # cost[i][j] = -1 for i > j + + cost = [[0, 15, 80, 90], # cost[i][j] is the cost of + [-1, 0, 40, 50], # going from i --> j + [-1, -1, 0, 70], + [-1, -1, -1, 0]] # cost[i][j] = -1 for i > j total_len = len(cost) - + mcost = min_cost(cost) assert mcost == 65 - + print("The Minimum cost to reach station %d is %d" % (total_len, mcost)) diff --git a/algorithms/dp/planting_trees.py b/algorithms/dp/planting_trees.py index c1efd4e64..56b99cde7 100644 --- a/algorithms/dp/planting_trees.py +++ b/algorithms/dp/planting_trees.py @@ -1,20 +1,24 @@ """ -An even number of trees are left along one side of a country road. You've been assigned the job to -plant these trees at an even interval on both sides of the road. The length L and width W of the road -are variable, and a pair of trees must be planted at the beginning (at 0) and at the end (at L) of -the road. Only one tree can be moved at a time. The goal is to calculate the lowest amount of -distance that the trees have to be moved before they are all in a valid position. +An even number of trees are left along one side of a country road. +You've been assigned the job to plant these trees at an even interval on +both sides of the road. The length L and width W of the road are variable, +and a pair of trees must be planted at the beginning (at 0) +and at the end (at L) of the road. Only one tree can be moved at a time. +The goal is to calculate the lowest amount of distance that the trees +have to be moved before they are all in a valid position. """ from math import sqrt -import sys + def planting_trees(trees, L, W): """ - Returns the minimum distance that trees have to be moved before they are all in a valid state. + Returns the minimum distance that trees have to be moved before they + are all in a valid state. Parameters: - tree (list): A sorted list of integers with all trees' position along the road. + tree (list): A sorted list of integers with all trees' + position along the road. L (int): An integer with the length of the road. W (int): An integer with the width of the road. @@ -42,4 +46,4 @@ def planting_trees(trees, L, W): cmatrix[ri][li-1] + abs(trees[li + ri]-target_locations[li-1]) ) - return cmatrix[n_pairs][n_pairs] \ No newline at end of file + return cmatrix[n_pairs][n_pairs] diff --git a/algorithms/graph/path_between_two_vertices_in_digraph.py b/algorithms/graph/path_between_two_vertices_in_digraph.py index ee6ac418f..f85513320 100644 --- a/algorithms/graph/path_between_two_vertices_in_digraph.py +++ b/algorithms/graph/path_between_two_vertices_in_digraph.py @@ -1,30 +1,31 @@ from collections import defaultdict + class Graph: - def __init__(self,v): + def __init__(self, v): self.v = v self.graph = defaultdict(list) self.has_path = False - def add_edge(self,u,v): + def add_edge(self, u, v): self.graph[u].append(v) - def dfs(self,x,y): + def dfs(self, x, y): visited = [False] * self.v - self.dfsutil(visited,x,y,) + self.dfsutil(visited, x, y,) - def dfsutil(self,visited,x,y): + def dfsutil(self, visited, x, y): visited[x] = True for i in self.graph[x]: if y in self.graph[x]: self.has_path = True return if(not(visited[i])): - self.dfsutil(visited,x,i) + self.dfsutil(visited, x, i) - def is_reachable(self,x,y): + def is_reachable(self, x, y): self.has_path = False - self.dfs(x,y) + self.dfs(x, y) return self.has_path @@ -36,16 +37,16 @@ def is_reachable(self,x,y): g.add_edge(2, 0) g.add_edge(2, 3) g.add_edge(3, 3) - -u =1; v = 3 - + +u, v = 1, 3 + +if g.is_reachable(u, v): + print("There is a path from %d to %d" % (u, v)) +else: + print("There is no path from %d to %d" % (u, v)) + +u, v = 1, 3 if g.is_reachable(u, v): - print("There is a path from %d to %d" % (u,v)) -else : - print("There is no path from %d to %d" % (u,v)) - -u = 3; v = 1 -if g.is_reachable(u, v) : - print("There is a path from %d to %d" % (u,v)) -else : - print("There is no path from %d to %d" % (u,v)) + print("There is a path from %d to %d" % (u, v)) +else: + print("There is no path from %d to %d" % (u, v)) diff --git a/algorithms/maths/diffie_hellman_key_exchange.py b/algorithms/maths/diffie_hellman_key_exchange.py index 5fed8efa1..91f4e8229 100644 --- a/algorithms/maths/diffie_hellman_key_exchange.py +++ b/algorithms/maths/diffie_hellman_key_exchange.py @@ -1,6 +1,7 @@ import math from random import randint + """ Code from /algorithms/maths/prime_check.py, written by 'goswami-rahul' and 'Hai Honag Dang' @@ -37,7 +38,7 @@ def find_order(a, n): 1 is the order of of 1 """ else: if (math.gcd(a, n) != 1): - print ("a and n should be relative prime!") + print("a and n should be relative prime!") return -1 else: for i in range(1, n): @@ -45,6 +46,7 @@ def find_order(a, n): return i return -1 + """ Euler's totient function, also known as phi-function ϕ(n), counts the number of integers between 1 and n inclusive, @@ -55,15 +57,16 @@ def find_order(a, n): def euler_totient(n): """Euler's totient function or Phi function. Time Complexity: O(sqrt(n)).""" - result = n; + result = n for i in range(2, int(n ** 0.5) + 1): if n % i == 0: while n % i == 0: n //= i result -= result // i if n > 1: - result -= result // n; - return result; + result -= result // n + return result + """ For positive integer n and given integer a that satisfies gcd(a, n) = 1, @@ -81,7 +84,7 @@ def find_primitive_root(n): phi = euler_totient(n) p_root_list = [] """ It will return every primitive roots of n. """ - for i in range (1, n): + for i in range(1, n): if (math.gcd(i, n) != 1): continue """ To have order, a and n must be @@ -103,7 +106,8 @@ def find_primitive_root(n): For example, given a, b and n, it is easy to calculate x that satisfies (a^b) ≡ x (mod n). However, it is very hard to calculate x that satisfies (a^x) ≡ b (mod n). -For using this method, large prime number p and its primitive root a must be given. +For using this method, large prime number p and its primitive root a +must be given. """ def alice_private_key(p): @@ -132,7 +136,7 @@ def bob_public_key(b_pr_k, a, p): with his private key. This is open to public""" return pow(a, b_pr_k) % p - + def alice_shared_key(b_pu_k, a_pr_k, p): """ Alice calculate secret key shared with Bob, @@ -148,12 +152,12 @@ def bob_shared_key(a_pu_k, b_pr_k, p): return pow(a_pu_k, b_pr_k) % p -def diffie_hellman_key_exchange(a, p, option = None): - if (option != None): +def diffie_hellman_key_exchange(a, p, option=None): + if (option is not None): option = 1 """ Print explanation of process when option parameter is given """ - if (prime_check(p) == False): + if (prime_check(p) is False): print("%d is not a prime number" % p) return False """p must be large prime number""" @@ -165,26 +169,25 @@ def diffie_hellman_key_exchange(a, p, option = None): print("%d is not a primitive root of %d" % (a, p)) return False """ a must be primitive root of p """ - + a_pr_k = alice_private_key(p) a_pu_k = alice_public_key(a_pr_k, a, p) - - + b_pr_k = bob_private_key(p) b_pu_k = bob_public_key(b_pr_k, a, p) - + if (option == 1): - print ("Private key of Alice = %d" % a_pr_k) - print ("Public key of Alice = %d" % a_pu_k) - print ("Private key of Bob = %d" % b_pr_k) - print ("Public key of Bob = %d" % b_pu_k) + print("Private key of Alice = %d" % a_pr_k) + print("Public key of Alice = %d" % a_pu_k) + print("Private key of Bob = %d" % b_pr_k) + print("Public key of Bob = %d" % b_pu_k) """ In here, Alice send her public key to Bob, and Bob also send his public key to Alice.""" a_sh_k = alice_shared_key(b_pu_k, a_pr_k, p) b_sh_k = bob_shared_key(a_pu_k, b_pr_k, p) - print ("Shared key calculated by Alice = %d" % a_sh_k) - print ("Shared key calculated by Bob = %d" % b_sh_k) - + print("Shared key calculated by Alice = %d" % a_sh_k) + print("Shared key calculated by Bob = %d" % b_sh_k) + return (a_sh_k == b_sh_k) diff --git a/algorithms/maths/polynomial.py b/algorithms/maths/polynomial.py index 55b59dd91..07a0c3b8a 100644 --- a/algorithms/maths/polynomial.py +++ b/algorithms/maths/polynomial.py @@ -12,7 +12,7 @@ class Monomial: record the details of all variables that a typical monomial is composed of. """ - def __init__(self, variables: Dict[int, int], coeff: Union[int, float, Fraction, None]=None) -> None: + def __init__(self, variables: Dict[int, int], coeff: Union[int, float, Fraction, None]= None) -> None: ''' Create a monomial in the given variables: Examples: @@ -63,7 +63,6 @@ def _rationalize_if_possible(num): else: return num - # def equal_upto_scalar(self, other: Monomial) -> bool: def equal_upto_scalar(self, other) -> bool: """ @@ -353,7 +352,7 @@ def __add__(self, other: Union[int, float, Fraction, Monomial]): return Polynomial([z for z in monos]) elif isinstance(other, Polynomial): temp = list(z for z in {m.clone() for m in self.all_monomials()}) - + p = Polynomial(temp) for o in other.all_monomials(): p = p.__add__(o.clone()) @@ -391,7 +390,7 @@ def __sub__(self, other: Union[int, float, Fraction, Monomial]): for o in other.all_monomials(): p = p.__sub__(o.clone()) return p - + else: raise ValueError('Can only subtract int, float, Fraction, Monomials, or Polynomials from Polynomials.') return @@ -531,4 +530,3 @@ def __str__(self) -> str: the polynomial. """ return ' + '.join(str(m) for m in self.all_monomials() if m.coeff != Fraction(0, 1)) - diff --git a/algorithms/maths/symmetry_group_cycle_index.py b/algorithms/maths/symmetry_group_cycle_index.py index 5f3872ac8..e6d01847e 100644 --- a/algorithms/maths/symmetry_group_cycle_index.py +++ b/algorithms/maths/symmetry_group_cycle_index.py @@ -1,72 +1,72 @@ -from polynomial import ( Monomial, Polynomial ) +from polynomial import (Monomial, Polynomial) from gcd import lcm from fractions import Fraction from typing import Dict, Union """ - The significance of the cycle index (polynomial) of symmetry group - is deeply rooted in counting the number of configurations - of an object excluding those that are symmetric (in terms of permutations). +The significance of the cycle index (polynomial) of symmetry group +is deeply rooted in counting the number of configurations +of an object excluding those that are symmetric (in terms of permutations). - For example, the following problem can be solved as a direct - application of the cycle index polynomial of the symmetry - group. +For example, the following problem can be solved as a direct +application of the cycle index polynomial of the symmetry +group. - Note: I came across this problem as a Google's foo.bar challenge at Level 5 - and solved it using a purely Group Theoretic approach. :) +Note: I came across this problem as a Google's foo.bar challenge at Level 5 +and solved it using a purely Group Theoretic approach. :) +----- - ----- +Problem: - Problem: +Given positive integers +w, h, and s, +compute the number of distinct 2D +grids of dimensions w x h that contain +entries from {0, 1, ..., s-1}. +Note that two grids are defined +to be equivalent if one can be +obtained from the other by +switching rows and columns +some number of times. - Given positive integers - w, h, and s, - compute the number of distinct 2D - grids of dimensions w x h that contain - entries from {0, 1, ..., s-1}. - Note that two grids are defined - to be equivalent if one can be - obtained from the other by - switching rows and columns - some number of times. +----- - ----- +Approach: - Approach: +Compute the cycle index (polynomials) +of S_w, and S_h, i.e. the Symmetry +group on w and h symbols respectively. - Compute the cycle index (polynomials) - of S_w, and S_h, i.e. the Symmetry - group on w and h symbols respectively. +Compute the product of the two +cycle indices while combining two +monomials in such a way that +for any pair of cycles c1, and c2 +in the elements of S_w X S_h, +the resultant monomial contains +terms of the form: +$$ x_{lcm(|c1|, |c2|)}^{gcd(|c1|, |c2|)} $$ - Compute the product of the two - cycle indices while combining two - monomials in such a way that - for any pair of cycles c1, and c2 - in the elements of S_w X S_h, - the resultant monomial contains - terms of the form: - $$ x_{lcm(|c1|, |c2|)}^{gcd(|c1|, |c2|)} $$ +Return the specialization of +the product of cycle indices +at x_i = s (for all the valid i). - Return the specialization of - the product of cycle indices - at x_i = s (for all the valid i). +----- - ----- +Code: - Code: +def solve(w, h, s): - def solve(w, h, s): +s1 = get_cycle_index_sym(w) +s2 = get_cycle_index_sym(h) - s1 = get_cycle_index_sym(w) - s2 = get_cycle_index_sym(h) - - result = cycle_product_for_two_polynomials(s1, s2, s) - - return str(result) +result = cycle_product_for_two_polynomials(s1, s2, s) + +return str(result) """ + def cycle_product(m1: Monomial, m2: Monomial) -> Monomial: """ Given two monomials (from the @@ -132,14 +132,14 @@ def get_cycle_index_sym(n: int) -> Polynomial: """ if n < 0: - raise ValueError('n should be a non-negative integer.') + raise ValueError('n should be a non-negative integer.') memo = { 0: Polynomial([ Monomial({}, Fraction(1, 1)) ]), 1: Polynomial([ - Monomial({1:1}, Fraction(1, 1)) + Monomial({1: 1}, Fraction(1, 1)) ]), 2: Polynomial([ Monomial({1: 2}, Fraction(1, 2)), @@ -148,14 +148,14 @@ def get_cycle_index_sym(n: int) -> Polynomial: 3: Polynomial([ Monomial({1: 3}, Fraction(1, 6)), Monomial({1: 1, 2: 1}, Fraction(1, 2)), - Monomial({3:1}, Fraction(1, 3)) + Monomial({3: 1}, Fraction(1, 3)) ]), 4: Polynomial([ - Monomial({1:4}, Fraction(1, 24)), - Monomial({2:1, 1:2},Fraction(1, 4)), - Monomial({3:1, 1:1}, Fraction(1, 3)), - Monomial({2:2}, Fraction(1, 8)), - Monomial({4:1}, Fraction(1, 4)), + Monomial({1: 4}, Fraction(1, 24)), + Monomial({2: 1, 1: 2}, Fraction(1, 4)), + Monomial({3: 1, 1: 1}, Fraction(1, 3)), + Monomial({2: 2}, Fraction(1, 8)), + Monomial({4: 1}, Fraction(1, 4)), ]) } result = cycle_index_sym_helper(n, memo) diff --git a/algorithms/matrix/bomb_enemy.py b/algorithms/matrix/bomb_enemy.py index debcf5d81..c8412635d 100644 --- a/algorithms/matrix/bomb_enemy.py +++ b/algorithms/matrix/bomb_enemy.py @@ -17,8 +17,10 @@ return 3. (Placing a bomb at (1,1) kills 3 enemies) """ + def max_killed_enemies(grid): - if not grid: return 0 + if not grid: + return 0 m, n = len(grid), len(grid[0]) max_killed = 0 row_e, col_e = 0, [0] * n @@ -38,6 +40,7 @@ def max_killed_enemies(grid): return max_killed + # calculate killed enemies for row i from column j def row_kills(grid, i, j): num = 0 @@ -48,6 +51,7 @@ def row_kills(grid, i, j): j += 1 return num + # calculate killed enemies for column j from row i def col_kills(grid, i, j): num = 0 @@ -57,7 +61,6 @@ def col_kills(grid, i, j): num += 1 i += 1 return num - # ----------------- TESTS ------------------------- @@ -66,14 +69,16 @@ def col_kills(grid, i, j): Testsuite for the project """ -import unittest +import unittest + class TestBombEnemy(unittest.TestCase): def test_3x4(self): - grid1 = [["0","E","0","0"], - ["E","0","W","E"], - ["0","E","0","0"]] - self.assertEqual(3,max_killed_enemies(grid1)) + grid1 = [["0", "E", "0", "0"], + ["E", "0", "W", "E"], + ["0", "E", "0", "0"]] + self.assertEqual(3, max_killed_enemies(grid1)) + def test_4x4(self): grid1 = [ ["0", "E", "0", "E"], @@ -85,9 +90,9 @@ def test_4x4(self): ["E", "0", "0", "0"], ["E", "0", "W", "E"], ["0", "E", "0", "0"]] - self.assertEqual(5,max_killed_enemies(grid1)) - self.assertEqual(3,max_killed_enemies(grid2)) + self.assertEqual(5, max_killed_enemies(grid1)) + self.assertEqual(3, max_killed_enemies(grid2)) + if __name__ == "__main__": unittest.main() - diff --git a/algorithms/matrix/cholesky_matrix_decomposition.py b/algorithms/matrix/cholesky_matrix_decomposition.py index 4b5e24e83..28ef5ea76 100644 --- a/algorithms/matrix/cholesky_matrix_decomposition.py +++ b/algorithms/matrix/cholesky_matrix_decomposition.py @@ -1,6 +1,8 @@ """ -Cholesky matrix decomposition is used to find the decomposition of a Hermitian positive-definite matrix A -into matrix V, so that V * V* = A, where V* denotes the conjugate transpose of L. +Cholesky matrix decomposition is used to find the decomposition of a +Hermitian positive-definite matrix A +into matrix V, so that V * V* = A, where V* denotes the conjugate +transpose of L. The dimensions of the matrix A must match. This method is mainly used for numeric solution of linear equations Ax = b. @@ -25,7 +27,8 @@ def cholesky_decomposition(A): """ :param A: Hermitian positive-definite matrix of type List[List[float]] - :return: matrix of type List[List[float]] if A can be decomposed, otherwise None + :return: matrix of type List[List[float]] if A can be decomposed, + otherwise None """ n = len(A) for ai in A: diff --git a/algorithms/matrix/copy_transform.py b/algorithms/matrix/copy_transform.py index 772a81c9c..99bfa462d 100644 --- a/algorithms/matrix/copy_transform.py +++ b/algorithms/matrix/copy_transform.py @@ -9,6 +9,7 @@ def rotate_clockwise(matrix): new[i].append(elem) return new + def rotate_counterclockwise(matrix): new = [] for row in matrix: @@ -20,6 +21,7 @@ def rotate_counterclockwise(matrix): new[i].append(elem) return new + def top_left_invert(matrix): new = [] for row in matrix: @@ -31,6 +33,7 @@ def top_left_invert(matrix): new[i].append(elem) return new + def bottom_left_invert(matrix): new = [] for row in reversed(matrix): @@ -42,6 +45,7 @@ def bottom_left_invert(matrix): new[i].append(elem) return new + if __name__ == '__main__': def print_matrix(matrix, name): print('{}:\n['.format(name)) diff --git a/algorithms/matrix/crout_matrix_decomposition.py b/algorithms/matrix/crout_matrix_decomposition.py index 46929b517..bfb859bb3 100644 --- a/algorithms/matrix/crout_matrix_decomposition.py +++ b/algorithms/matrix/crout_matrix_decomposition.py @@ -26,6 +26,7 @@ I think the complexity should be O(n^3). """ + def crout_matrix_decomposition(A): n = len(A) L = [[0.0] * n for i in range(n)] @@ -44,4 +45,4 @@ def crout_matrix_decomposition(A): if int(L[j][j]) == 0: L[j][j] = float(0.1**40) U[j][i] = float(tempU/L[j][j]) - return (L,U) + return (L, U) diff --git a/algorithms/matrix/matrix_exponentiation.py b/algorithms/matrix/matrix_exponentiation.py index f9673ce4a..2c836fe07 100644 --- a/algorithms/matrix/matrix_exponentiation.py +++ b/algorithms/matrix/matrix_exponentiation.py @@ -13,18 +13,20 @@ def multiply(matA: list, matB: list) -> list: return matC + def identity(n: int) -> list: """ Returns the Identity matrix of size n x n Time Complexity: O(n^2) """ I = [[0 for i in range(n)] for j in range(n)] - + for i in range(n): I[i][i] = 1 - + return I + def matrix_exponentiation(mat: list, n: int) -> list: """ Calculates mat^n by repeated squaring diff --git a/algorithms/matrix/rotate_image.py b/algorithms/matrix/rotate_image.py index f237fbf44..a8b5e4e77 100644 --- a/algorithms/matrix/rotate_image.py +++ b/algorithms/matrix/rotate_image.py @@ -25,9 +25,9 @@ def rotate(mat): if __name__ == "__main__": - mat = [[1,2,3], - [4,5,6], - [7,8,9]] + mat = [[1, 2, 3], + [4, 5, 6], + [7, 8, 9]] print(mat) rotate(mat) print(mat) diff --git a/algorithms/matrix/search_in_sorted_matrix.py b/algorithms/matrix/search_in_sorted_matrix.py index ce3711200..54502c33a 100644 --- a/algorithms/matrix/search_in_sorted_matrix.py +++ b/algorithms/matrix/search_in_sorted_matrix.py @@ -10,13 +10,13 @@ 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(): @@ -27,7 +27,7 @@ def main(): [12, 17, 20] ] key = 13 - print (mat) + print(mat) search_in_a_sorted_matrix(mat, len(mat), len(mat[0]), key) diff --git a/algorithms/matrix/sort_matrix_diagonally.py b/algorithms/matrix/sort_matrix_diagonally.py index 4e9e085d6..b32c71044 100644 --- a/algorithms/matrix/sort_matrix_diagonally.py +++ b/algorithms/matrix/sort_matrix_diagonally.py @@ -32,7 +32,7 @@ def sort_diagonally(mat: List[List[int]]) -> List[List[int]]: for i in range(len(mat)+len(mat[0])-1): # Process the rows if i+1 < len(mat): - # Initialize heap, set row and column + # Initialize heap, set row and column h = [] row = len(mat)-(i+1) col = 0 diff --git a/algorithms/matrix/sparse_dot_vector.py b/algorithms/matrix/sparse_dot_vector.py index 19053ec58..0cba0a575 100644 --- a/algorithms/matrix/sparse_dot_vector.py +++ b/algorithms/matrix/sparse_dot_vector.py @@ -1,7 +1,8 @@ #! /usr/bin/env python3 """ -Suppose we have very large sparse vectors, which contains a lot of zeros and double . +Suppose we have very large sparse vectors, which contains a lot of +zeros and double . find a data structure to store them get the dot product of them diff --git a/algorithms/matrix/sparse_mul.py b/algorithms/matrix/sparse_mul.py index c3b7b61e4..eeaae0eff 100644 --- a/algorithms/matrix/sparse_mul.py +++ b/algorithms/matrix/sparse_mul.py @@ -30,7 +30,8 @@ def multiply(self, a, b): :type B: List[List[int]] :rtype: List[List[int]] """ - if a is None or b is None: return None + if a is None or b is None: + return None m, n, l = len(a), len(b[0]), len(b[0]) if len(b) != n: raise Exception("A's column number must be equal to B's row number.") @@ -39,7 +40,8 @@ def multiply(self, a, b): for k, eleA in enumerate(row): if eleA: for j, eleB in enumerate(b[k]): - if eleB: c[i][j] += eleA * eleB + if eleB: + c[i][j] += eleA * eleB return c @@ -50,7 +52,8 @@ def multiply(self, a, b): :type B: List[List[int]] :rtype: List[List[int]] """ - if a is None or b is None: return None + if a is None or b is None: + return None m, n, l = len(a), len(a[0]), len(b[0]) if len(b) != n: raise Exception("A's column number must be equal to B's row number.") @@ -59,7 +62,8 @@ def multiply(self, a, b): for k, row in enumerate(b): table_b[k] = {} for j, eleB in enumerate(row): - if eleB: table_b[k][j] = eleB + if eleB: + table_b[k][j] = eleB for i, row in enumerate(a): for k, eleA in enumerate(row): if eleA: @@ -67,6 +71,7 @@ def multiply(self, a, b): c[i][j] += eleA * eleB return c + # Python solution with two tables (~196ms): def multiply(self, a, b): """ @@ -74,7 +79,8 @@ def multiply(self, a, b): :type B: List[List[int]] :rtype: List[List[int]] """ - if a is None or b is None: return None + if a is None or b is None: + return None m, n = len(a), len(b[0]) if len(b) != n: raise Exception("A's column number must be equal to B's row number.") @@ -83,17 +89,20 @@ def multiply(self, a, b): for i, row in enumerate(a): for j, ele in enumerate(row): if ele: - if i not in table_a: table_a[i] = {} + if i not in table_a: + table_a[i] = {} table_a[i][j] = ele for i, row in enumerate(b): for j, ele in enumerate(row): if ele: - if i not in table_b: table_b[i] = {} + if i not in table_b: + table_b[i] = {} table_b[i][j] = ele c = [[0 for j in range(l)] for i in range(m)] for i in table_a: for k in table_a[i]: - if k not in table_b: continue + if k not in table_b: + continue for j in table_b[k]: c[i][j] += table_a[i][k] * table_b[k][j] return c diff --git a/algorithms/matrix/sudoku_validator.py b/algorithms/matrix/sudoku_validator.py index 7bda6e424..257fcce8e 100644 --- a/algorithms/matrix/sudoku_validator.py +++ b/algorithms/matrix/sudoku_validator.py @@ -1,12 +1,19 @@ """ -Write a function validSolution/ValidateSolution/valid_solution() that accepts a 2D array representing a Sudoku board, and returns true if it is a valid solution, or false otherwise. The cells of the sudoku board may also contain 0's, which will represent empty cells. Boards containing one or more zeroes are considered to be invalid solutions. -The board is always 9 cells by 9 cells, and every cell only contains integers from 0 to 9. +Write a function validSolution/ValidateSolution/valid_solution() +that accepts a 2D array representing a Sudoku board, and returns true +if it is a valid solution, or false otherwise. The cells of the sudoku +board may also contain 0's, which will represent empty cells. +Boards containing one or more zeroes are considered to be invalid solutions. +The board is always 9 cells by 9 cells, and every cell only contains integers +from 0 to 9. (More info at: http://en.wikipedia.org/wiki/Sudoku) """ # Using dict/hash-table from collections import defaultdict + + def valid_solution_hashtable(board): for i in range(len(board)): dict_row = defaultdict(int) @@ -31,7 +38,7 @@ def valid_solution_hashtable(board): grid_add = 0 for k in range(3): for l in range(3): - grid_add += board[i*3+k][j*3+l] + grid_add += board[i * 3 + k][j * 3 + l] if grid_add != 45: return False return True @@ -65,7 +72,7 @@ def valid_solution(board): # Using set -def valid_solution_set (board): +def valid_solution_set(board): valid = set(range(1, 10)) for row in board: diff --git a/algorithms/matrix/sum_sub_squares.py b/algorithms/matrix/sum_sub_squares.py index 45de7f10c..1231547ef 100644 --- a/algorithms/matrix/sum_sub_squares.py +++ b/algorithms/matrix/sum_sub_squares.py @@ -1,6 +1,6 @@ -# Function to find sum of all -# sub-squares of size k x k in a given -# square matrix of size n x n +# Function to find sum of all +# sub-squares of size k x k in a given +# square matrix of size n x n def sum_sub_squares(matrix, k): n = len(matrix) result = [[0 for i in range(k)] for j in range(k)] @@ -8,17 +8,16 @@ def sum_sub_squares(matrix, k): if k > n: return for i in range(n - k + 1): - l = 0 + l = 0 for j in range(n - k + 1): sum = 0 - - # Calculate and print sum of current sub-square + + # Calculate and print sum of current sub-square for p in range(i, k + i): for q in range(j, k + j): sum += matrix[p][q] - + result[i][l] = sum - l += 1 - - return result + l += 1 + return result diff --git a/algorithms/queues/max_sliding_window.py b/algorithms/queues/max_sliding_window.py index 7d1a45f11..74db65e11 100644 --- a/algorithms/queues/max_sliding_window.py +++ b/algorithms/queues/max_sliding_window.py @@ -18,6 +18,8 @@ """ import collections + + def max_sliding_window(arr, k): qi = collections.deque() # queue storing indexes of elements result = [] diff --git a/algorithms/queues/priority_queue.py b/algorithms/queues/priority_queue.py index 76b08156e..c573ac3cc 100644 --- a/algorithms/queues/priority_queue.py +++ b/algorithms/queues/priority_queue.py @@ -53,4 +53,3 @@ def pop(self): """ # remove and return the first node from the queue return self.priority_queue_list.pop().data - diff --git a/algorithms/queues/queue.py b/algorithms/queues/queue.py index feab56b62..b3ccb4e7f 100644 --- a/algorithms/queues/queue.py +++ b/algorithms/queues/queue.py @@ -13,6 +13,8 @@ * peek() returns the front element of the queue. """ from abc import ABCMeta, abstractmethod + + class AbstractQueue(metaclass=ABCMeta): def __init__(self): @@ -96,6 +98,7 @@ def __init__(self, value): self.value = value self.next = None + class LinkedListQueue(AbstractQueue): def __init__(self): diff --git a/algorithms/queues/zigzagiterator.py b/algorithms/queues/zigzagiterator.py index a8b571f94..5b1e50371 100644 --- a/algorithms/queues/zigzagiterator.py +++ b/algorithms/queues/zigzagiterator.py @@ -5,25 +5,28 @@ def __init__(self, v1, v2): :type v1: List[int] :type v2: List[int] """ - self.queue=[_ for _ in (v1,v2) if _] + self.queue = [_ for _ in (v1, v2) if _] print(self.queue) def next(self): """ :rtype: int """ - v=self.queue.pop(0) - ret=v.pop(0) - if v: self.queue.append(v) + v = self.queue.pop(0) + ret = v.pop(0) + if v: + self.queue.append(v) return ret def has_next(self): """ :rtype: bool """ - if self.queue: return True + if self.queue: + return True return False + l1 = [1, 2] l2 = [3, 4, 5, 6] it = ZigZagIterator(l1, l2) diff --git a/algorithms/set/find_keyboard_row.py b/algorithms/set/find_keyboard_row.py index e00e3683d..c15dcdadf 100644 --- a/algorithms/set/find_keyboard_row.py +++ b/algorithms/set/find_keyboard_row.py @@ -8,6 +8,7 @@ Reference: https://leetcode.com/problems/keyboard-row/description/ """ + def find_keyboard_row(words): """ :type words: List[str] diff --git a/algorithms/stack/is_consecutive.py b/algorithms/stack/is_consecutive.py index f73ddffc7..44e3f90f5 100644 --- a/algorithms/stack/is_consecutive.py +++ b/algorithms/stack/is_consecutive.py @@ -18,14 +18,15 @@ """ import collections + def first_is_consecutive(stack): storage_stack = [] for i in range(len(stack)): first_value = stack.pop() - if len(stack) == 0: # Case odd number of values in stack + if len(stack) == 0: # Case odd number of values in stack return True second_value = stack.pop() - if first_value - second_value != 1: # Not consecutive + if first_value - second_value != 1: # Not consecutive return False stack.append(second_value) # Backup second value storage_stack.append(first_value) @@ -35,14 +36,15 @@ def first_is_consecutive(stack): stack.append(storage_stack.pop()) return True + def second_is_consecutive(stack): q = collections.deque() for i in range(len(stack)): first_value = stack.pop() - if len(stack) == 0: # Case odd number of values in stack + if len(stack) == 0: # Case odd number of values in stack return True second_value = stack.pop() - if first_value - second_value != 1: # Not consecutive + if first_value - second_value != 1: # Not consecutive return False stack.append(second_value) # Backup second value q.append(first_value) diff --git a/algorithms/stack/is_sorted.py b/algorithms/stack/is_sorted.py index 0a69d83f9..b3c3337e5 100644 --- a/algorithms/stack/is_sorted.py +++ b/algorithms/stack/is_sorted.py @@ -9,6 +9,8 @@ bottom [1, 2, 3, 4, 5, 6] top The function should return true """ + + def is_sorted(stack): storage_stack = [] for i in range(len(stack)): diff --git a/algorithms/stack/longest_abs_path.py b/algorithms/stack/longest_abs_path.py index 67aad5a6a..edc4ee8b0 100644 --- a/algorithms/stack/longest_abs_path.py +++ b/algorithms/stack/longest_abs_path.py @@ -1,37 +1,37 @@ # def lengthLongestPath(input): - # maxlen = 0 - # pathlen = {0: 0} - # for line in input.splitlines(): - # print("---------------") - # print("line:", line) - # name = line.strip('\t') - # print("name:", name) - # depth = len(line) - len(name) - # print("depth:", depth) - # if '.' in name: - # maxlen = max(maxlen, pathlen[depth] + len(name)) - # else: - # pathlen[depth + 1] = pathlen[depth] + len(name) + 1 - # print("maxlen:", maxlen) - # return maxlen +# maxlen = 0 +# pathlen = {0: 0} +# for line in input.splitlines(): +# print("---------------") +# print("line:", line) +# name = line.strip('\t') +# print("name:", name) +# depth = len(line) - len(name) +# print("depth:", depth) +# if '.' in name: +# maxlen = max(maxlen, pathlen[depth] + len(name)) +# else: +# pathlen[depth + 1] = pathlen[depth] + len(name) + 1 +# print("maxlen:", maxlen) +# return maxlen # def lengthLongestPath(input): - # paths = input.split("\n") - # level = [0] * 10 - # maxLength = 0 - # for path in paths: - # print("-------------") - # levelIdx = path.rfind("\t") - # print("Path: ", path) - # print("path.rfind(\\t)", path.rfind("\t")) - # print("levelIdx: ", levelIdx) - # print("level: ", level) - # level[levelIdx + 1] = level[levelIdx] + len(path) - levelIdx + 1 - # print("level: ", level) - # if "." in path: - # maxLength = max(maxLength, level[levelIdx+1] - 1) - # print("maxlen: ", maxLength) - # return maxLength +# paths = input.split("\n") +# level = [0] * 10 +# maxLength = 0 +# for path in paths: +# print("-------------") +# levelIdx = path.rfind("\t") +# print("Path: ", path) +# print("path.rfind(\\t)", path.rfind("\t")) +# print("levelIdx: ", levelIdx) +# print("level: ", level) +# level[levelIdx + 1] = level[levelIdx] + len(path) - levelIdx + 1 +# print("level: ", level) +# if "." in path: +# maxLength = max(maxLength, level[levelIdx+1] - 1) +# print("maxlen: ", maxLength) +# return maxLength def length_longest_path(input): """ @@ -57,9 +57,9 @@ def length_longest_path(input): max_len = max(max_len, curr_len-1) # -1 is to minus one '/' return max_len -st= "dir\n\tsubdir1\n\t\tfile1.ext\n\t\tsubsubdirectory1\n\tsubdir2\n\t\tsubsubdir2\n\t\t\tfile2.ext" + +st = "dir\n\tsubdir1\n\t\tfile1.ext\n\t\tsubsubdirectory1\n\tsubdir2\n\t\tsubsubdir2\n\t\t\tfile2.ext" st2 = "a\n\tb1\n\t\tf1.txt\n\taaaaa\n\t\tf2.txt" print("path:", st2) print("answer:", length_longest_path(st2)) - diff --git a/algorithms/stack/ordered_stack.py b/algorithms/stack/ordered_stack.py index 7a30d143e..94191d6cf 100644 --- a/algorithms/stack/ordered_stack.py +++ b/algorithms/stack/ordered_stack.py @@ -1,33 +1,36 @@ -#The stack remains always ordered such that the highest value is at the top and the lowest at the bottom +# The stack remains always ordered such that the highest value +# is at the top and the lowest at the bottom + class OrderedStack: - def __init__(self): - self.items = [] - - def is_empty(self): - return self.items == [] - - def push_t(self, item): - self.items.append(item) - - def push(self, item): #push method to maintain order when pushing new elements - temp_stack = OrderedStack() - if self.is_empty() or item > self.peek(): - self.push_t(item) - else: - while item < self.peek() and not self.is_empty(): - temp_stack.push_t(self.pop()) - self.push_t(item) - while not temp_stack.is_empty(): - self.push_t(temp_stack.pop()) - - def pop(self): - if self.is_empty(): - raise IndexError("Stack is empty") - return self.items.pop() - - def peek(self): - return self.items[len(self.items) - 1] - - def size(self): - return len(self.items) + def __init__(self): + self.items = [] + + def is_empty(self): + return self.items == [] + + def push_t(self, item): + self.items.append(item) + + # push method to maintain order when pushing new elements + def push(self, item): + temp_stack = OrderedStack() + if self.is_empty() or item > self.peek(): + self.push_t(item) + else: + while item < self.peek() and not self.is_empty(): + temp_stack.push_t(self.pop()) + self.push_t(item) + while not temp_stack.is_empty(): + self.push_t(temp_stack.pop()) + + def pop(self): + if self.is_empty(): + raise IndexError("Stack is empty") + return self.items.pop() + + def peek(self): + return self.items[len(self.items) - 1] + + def size(self): + return len(self.items) diff --git a/algorithms/stack/remove_min.py b/algorithms/stack/remove_min.py index 1cd8fc6ae..ce635a4ab 100644 --- a/algorithms/stack/remove_min.py +++ b/algorithms/stack/remove_min.py @@ -8,6 +8,8 @@ bottom [2, 8, 3, 7, 3] top """ + + def remove_min(stack): storage_stack = [] if len(stack) == 0: # Stack is empty diff --git a/algorithms/stack/simplify_path.py b/algorithms/stack/simplify_path.py index fed4385a6..09f9de826 100644 --- a/algorithms/stack/simplify_path.py +++ b/algorithms/stack/simplify_path.py @@ -7,8 +7,9 @@ * Did you consider the case where path = "/../"? In this case, you should return "/". -* Another corner case is the path might contain multiple slashes '/' together, such as "/home//foo/". - In this case, you should ignore redundant slashes and return "/home/foo". +* Another corner case is the path might contain multiple slashes '/' together, + such as "/home//foo/". In this case, you should ignore redundant + slashes and return "/home/foo". """ def simplify_path(path): """ diff --git a/algorithms/stack/stack.py b/algorithms/stack/stack.py index d1a148e36..a79aec490 100644 --- a/algorithms/stack/stack.py +++ b/algorithms/stack/stack.py @@ -12,6 +12,8 @@ It needs no parameters and returns a boolean value. """ from abc import ABCMeta, abstractmethod + + class AbstractStack(metaclass=ABCMeta): """Abstract Class for Stacks.""" def __init__(self): diff --git a/algorithms/stack/stutter.py b/algorithms/stack/stutter.py index 30314992c..ceb6451d4 100644 --- a/algorithms/stack/stutter.py +++ b/algorithms/stack/stutter.py @@ -13,6 +13,7 @@ """ import collections + def first_stutter(stack): storage_stack = [] for i in range(len(stack)): @@ -24,6 +25,7 @@ def first_stutter(stack): return stack + def second_stutter(stack): q = collections.deque() # Put all values into queue from stack diff --git a/algorithms/stack/switch_pairs.py b/algorithms/stack/switch_pairs.py index f5db2280d..d8b22dc08 100644 --- a/algorithms/stack/switch_pairs.py +++ b/algorithms/stack/switch_pairs.py @@ -4,13 +4,15 @@ For example, if the stack initially stores these values: bottom [3, 8, 17, 9, 1, 10] top -Your function should switch the first pair (3, 8), the second pair (17, 9), ...: +Your function should switch the first pair (3, 8), +the second pair (17, 9), ...: bottom [8, 3, 9, 17, 10, 1] top if there are an odd number of values in the stack, the value at the top of the stack is not moved: For example: bottom [3, 8, 17, 9, 1] top -It would again switch pairs of values, but the value at the top of the stack (1) +It would again switch pairs of values, but the value at the +top of the stack (1) would not be moved bottom [8, 3, 9, 17, 1] top @@ -20,6 +22,7 @@ """ import collections + def first_switch_pairs(stack): storage_stack = [] for i in range(len(stack)): @@ -36,6 +39,7 @@ def first_switch_pairs(stack): stack.append(first) return stack + def second_switch_pairs(stack): q = collections.deque() # Put all values into queue from stack diff --git a/algorithms/stack/valid_parenthesis.py b/algorithms/stack/valid_parenthesis.py index b62ac02c4..8fdb861b5 100644 --- a/algorithms/stack/valid_parenthesis.py +++ b/algorithms/stack/valid_parenthesis.py @@ -6,6 +6,8 @@ The brackets must close in the correct order, "()" and "()[]{}" are all valid but "(]" and "([)]" are not. """ + + def is_valid(s: str) -> bool: stack = [] dic = {")": "(", diff --git a/tests/test_iterative_segment_tree.py b/tests/test_iterative_segment_tree.py index 77c0283ba..66d222267 100644 --- a/tests/test_iterative_segment_tree.py +++ b/tests/test_iterative_segment_tree.py @@ -74,7 +74,7 @@ def __test_all_segments(self, arr, fnc): """ Test all possible segments in the tree :param arr: array to test - :param fnc: function of the segment tree + :param fnc: function of the segment tpree """ segment_tree = SegmentTree(arr, fnc) self.__test_segments_helper(segment_tree, fnc, arr) From 3259a07a955661b51082f2f933888eb62c277c22 Mon Sep 17 00:00:00 2001 From: Christofer Nolander Date: Mon, 7 Mar 2022 12:17:05 +0100 Subject: [PATCH 079/101] Pylint refactor (#851) * refactor(pylint): unionfind/count_islands.py improves pylint score from 3.71 to 10.00 fixes https://github.com/KTH-Software-Engineering-DD2480/algorithms/issues/2 * refactor(pylint): algorithms/search/*.py improves pylint score from 5.83 to 10.00. fixes https://github.com/KTH-Software-Engineering-DD2480/algorithms/issues/7 * feat: improving pylint score * refactor dp.combination_sum: lint score from 5.42 to 9.13 * refactor(pylint): algorithms/graph/*.py Improves pylint score from 5.51 to 9.96. Score is lower than 10 due to duplication between maximum_flow_bfs.py and maximum_flow_dfs.py. However, due to the educational nature of this repo, keeping it as is will probably be benefitial as it reduces complexity while reading (having to jump between files). fixes https://github.com/KTH-Software-Engineering-DD2480/algorithms/issues/9 * refactor egg_drop, hosoya_triangle, min_cost_path and planting_trees * feat: improving pylint score of one_sparse_recovery * refactor: move tests from regex_matching to tests folder * refactor: lint score from 4.27 to 9.46 a reason that lint score isn't 10.00 is that the module is called dp, which in turn makes pylint raise invalid name for all files in dp. I leave that as it is, since I don't want to mess with the current folder structure. fixes #3 * Fix: Fixed lint error, lint value now aboe 8 * refactor: add docstring to misra, score 10 * fix (misra): add a newline * refactor: add docstring for one_sparse, score 9.33 * wip: refactor (pylint): algorithms/maths/ wip: #4 * Fix: pylint is above 8 for tree * refactor (pylint): algorithms/maths/ Finished improving pylint score for maths folder. fixes: #4 * Fixed: comments * fix: small intendation fix Co-authored-by: ekorre1001 Co-authored-by: Philip Salqvist Co-authored-by: psalqvist <63300368+psalqvist@users.noreply.github.com> Co-authored-by: Kubha99 <1kunalbhatnagar@gmail.com> Co-authored-by: mantaur --- algorithms/dp/__init__.py | 2 +- algorithms/dp/climbing_stairs.py | 24 +- algorithms/dp/coin_change.py | 31 +-- algorithms/dp/combination_sum.py | 56 +++-- algorithms/dp/edit_distance.py | 32 +-- algorithms/dp/egg_drop.py | 7 +- algorithms/dp/fib.py | 11 +- algorithms/dp/hosoya_triangle.py | 49 ++-- algorithms/dp/int_divide.py | 17 +- algorithms/dp/job_scheduling.py | 67 +++--- algorithms/dp/k_factor.py | 133 +++++------ algorithms/dp/longest_common_subsequence.py | 22 +- algorithms/dp/longest_increasing.py | 87 ++++---- algorithms/dp/matrix_chain_order.py | 29 ++- algorithms/dp/max_product_subarray.py | 25 +-- algorithms/dp/min_cost_path.py | 29 +-- algorithms/dp/num_decodings.py | 34 +-- algorithms/dp/planting_trees.py | 46 ++-- algorithms/dp/regex_matching.py | 127 ++++------- algorithms/dp/rod_cut.py | 22 +- algorithms/dp/word_break.py | 29 ++- algorithms/graph/__init__.py | 4 + algorithms/graph/all_pairs_shortest_path.py | 32 ++- algorithms/graph/bellman_ford.py | 40 ++-- algorithms/graph/check_bipartite.py | 39 ++-- .../graph/check_digraph_strongly_connected.py | 116 +++++----- algorithms/graph/clone_graph.py | 70 ++++-- .../count_connected_number_of_component.py | 43 ++-- algorithms/graph/cycle_detection.py | 41 ++-- algorithms/graph/dijkstra.py | 49 ++-- algorithms/graph/find_all_cliques.py | 21 +- algorithms/graph/find_path.py | 31 +-- algorithms/graph/graph.py | 92 ++++---- algorithms/graph/markov_chain.py | 31 ++- algorithms/graph/maximum_flow.py | 89 ++++---- algorithms/graph/maximum_flow_bfs.py | 45 ++-- algorithms/graph/maximum_flow_dfs.py | 42 ++-- algorithms/graph/minimum_spanning_tree.py | 209 ++++++++++-------- .../path_between_two_vertices_in_digraph.py | 95 ++++---- algorithms/graph/prims_minimum_spanning.py | 48 ++-- algorithms/graph/satisfiability.py | 91 +++++--- algorithms/graph/tarjan.py | 54 +++-- algorithms/graph/transitive_closure_dfs.py | 69 +++--- algorithms/graph/traversal.py | 60 ++--- algorithms/heap/binary_heap.py | 65 +++--- algorithms/heap/k_closest_points.py | 18 +- algorithms/heap/merge_sorted_k_lists.py | 24 +- algorithms/maths/__init__.py | 4 +- algorithms/maths/base_conversion.py | 37 ++-- algorithms/maths/chinese_remainder_theorem.py | 52 ++--- algorithms/maths/combination.py | 6 +- algorithms/maths/cosine_similarity.py | 23 +- algorithms/maths/decimal_to_binary_ip.py | 8 + .../maths/diffie_hellman_key_exchange.py | 133 ++++++----- algorithms/maths/euler_totient.py | 6 +- algorithms/maths/extended_gcd.py | 17 +- algorithms/maths/factorial.py | 3 + algorithms/maths/find_order_simple.py | 40 ++-- .../maths/find_primitive_root_simple.py | 68 +++--- algorithms/maths/gcd.py | 18 +- algorithms/maths/generate_strobogrammtic.py | 10 +- algorithms/maths/hailstone.py | 30 ++- algorithms/maths/is_strobogrammatic.py | 3 +- algorithms/maths/krishnamurthy_number.py | 11 +- algorithms/maths/magic_number.py | 13 +- algorithms/maths/modular_inverse.py | 14 +- algorithms/maths/next_bigger.py | 8 +- algorithms/maths/next_perfect_square.py | 12 +- algorithms/maths/nth_digit.py | 2 +- algorithms/maths/num_digits.py | 11 +- algorithms/maths/power.py | 33 +-- .../maths/primes_sieve_of_eratosthenes.py | 1 - algorithms/maths/pythagoras.py | 18 +- algorithms/maths/rabin_miller.py | 2 +- .../maths/recursive_binomial_coefficient.py | 6 +- algorithms/maths/rsa.py | 27 +-- algorithms/maths/summing_digits.py | 13 +- .../maths/symmetry_group_cycle_index.py | 21 +- algorithms/search/__init__.py | 4 + algorithms/search/binary_search.py | 52 +++-- algorithms/search/find_min_rotate.py | 11 +- algorithms/search/first_occurrence.py | 31 +-- algorithms/search/interpolation_search.py | 6 +- algorithms/search/jump_search.py | 41 ++-- algorithms/search/last_occurrence.py | 26 ++- algorithms/search/linear_search.py | 19 +- algorithms/search/next_greatest_letter.py | 20 +- algorithms/search/search_insert.py | 20 +- algorithms/search/search_rotate.py | 19 +- algorithms/search/ternary_search.py | 51 +++-- algorithms/search/two_sum.py | 50 +++-- algorithms/streaming/misra_gries.py | 75 ++++--- algorithms/streaming/one_sparse_recovery.py | 91 ++++---- algorithms/tree/avl/avl.py | 8 +- algorithms/tree/b_tree.py | 66 +++--- algorithms/tree/same_tree.py | 8 +- algorithms/tree/traversal/inorder.py | 11 +- algorithms/tree/traversal/preorder.py | 6 +- algorithms/unionfind/count_islands.py | 180 +++++++++------ tests/test_dp.py | 53 ++++- tests/test_graph.py | 71 ++++++ tests/test_heap.py | 4 +- 102 files changed, 2235 insertions(+), 1735 deletions(-) diff --git a/algorithms/dp/__init__.py b/algorithms/dp/__init__.py index ac56eda74..442120282 100644 --- a/algorithms/dp/__init__.py +++ b/algorithms/dp/__init__.py @@ -20,4 +20,4 @@ from .word_break import * from .int_divide import * from .k_factor import * -from .planting_trees import * \ No newline at end of file +from .planting_trees import * diff --git a/algorithms/dp/climbing_stairs.py b/algorithms/dp/climbing_stairs.py index 0c02efe51..9b90ae15c 100644 --- a/algorithms/dp/climbing_stairs.py +++ b/algorithms/dp/climbing_stairs.py @@ -1,23 +1,23 @@ """ You are climbing a stair case. -It takes n steps to reach to the top. +It takes `steps` number of steps to reach to the top. Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top? -Note: Given n will be a positive integer. +Note: Given argument `steps` will be a positive integer. """ # O(n) space -def climb_stairs(n): +def climb_stairs(steps): """ - :type n: int + :type steps: int :rtype: int """ arr = [1, 1] - for _ in range(1, n): + for _ in range(1, steps): arr.append(arr[-1] + arr[-2]) return arr[-1] @@ -25,8 +25,12 @@ def climb_stairs(n): # the above function can be optimized as: # O(1) space -def climb_stairs_optimized(n): - a = b = 1 - for _ in range(n): - a, b = b, a + b - return a +def climb_stairs_optimized(steps): + """ + :type steps: int + :rtype: int + """ + a_steps = b_steps = 1 + for _ in range(steps): + a_steps, b_steps = b_steps, a_steps + b_steps + return a_steps diff --git a/algorithms/dp/coin_change.py b/algorithms/dp/coin_change.py index a1d739d9b..379753b06 100644 --- a/algorithms/dp/coin_change.py +++ b/algorithms/dp/coin_change.py @@ -1,31 +1,34 @@ """ Problem -Given a value n, if we want to make change for N cents, -and we have infinite supply of each of -coins = {S1, S2, .. , Sm} valued coins, how many ways -can we make the change? -The order of coins doesn't matter. -For example, for n = 4 and coins = [1, 2, 3], there are -four solutions: +Given a value `value`, if we want to make change for `value` cents, and we have infinite +supply of each of coins = {S1, S2, .. , Sm} valued `coins`, how many ways can we make the change? +The order of `coins` doesn't matter. +For example, for `value` = 4 and `coins` = [1, 2, 3], there are four solutions: [1, 1, 1, 1], [1, 1, 2], [2, 2], [1, 3]. So output should be 4. -For n = 10 and coins = [2, 5, 3, 6], there are five solutions: +For `value` = 10 and `coins` = [2, 5, 3, 6], there are five solutions: + [2, 2, 2, 2, 2], [2, 2, 3, 3], [2, 2, 6], [2, 3, 5] and [5, 5]. So the output should be 5. -Time complexity: O(n * m) where n is the value and m is the number of coins +Time complexity: O(n * m) where n is the `value` and m is the number of `coins` Space complexity: O(n) """ +def count(coins, value): + """ Find number of combination of `coins` that adds upp to `value` -def count(coins, n): + Keyword arguments: + coins -- int[] + value -- int + """ # initialize dp array and set base case as 1 - dp = [1] + [0] * n + dp_array = [1] + [0] * value # fill dp in a bottom up manner for coin in coins: - for i in range(coin, n+1): - dp[i] += dp[i-coin] + for i in range(coin, value+1): + dp_array[i] += dp_array[i-coin] - return dp[n] + return dp_array[value] diff --git a/algorithms/dp/combination_sum.py b/algorithms/dp/combination_sum.py index 57587ff91..aaf0749e8 100644 --- a/algorithms/dp/combination_sum.py +++ b/algorithms/dp/combination_sum.py @@ -27,34 +27,48 @@ """ -dp = None - +DP = None def helper_topdown(nums, target): - global dp - if dp[target] != -1: - return dp[target] + """Generates DP and finds result. + + Keyword arguments: + nums -- positive integer array without duplicates + target -- integer describing what a valid combination should add to + """ + if DP[target] != -1: + return DP[target] res = 0 - for i in range(0, len(nums)): - if target >= nums[i]: - res += helper_topdown(nums, target - nums[i]) - dp[target] = res + for num in nums: + if target >= num: + res += helper_topdown(nums, target - num) + DP[target] = res return res def combination_sum_topdown(nums, target): - global dp - dp = [-1] * (target + 1) - dp[0] = 1 - return helper_topdown(nums, target) + """Find number of possible combinations in nums that add up to target, in top-down manner. + Keyword arguments: + nums -- positive integer array without duplicates + target -- integer describing what a valid combination should add to + """ + global DP + DP = [-1] * (target + 1) + DP[0] = 1 + return helper_topdown(nums, target) -# EDIT: The above solution is top-down. How about a bottom-up one? def combination_sum_bottom_up(nums, target): - comb = [0] * (target + 1) - comb[0] = 1 - for i in range(0, len(comb)): - for j in range(len(nums)): - if i - nums[j] >= 0: - comb[i] += comb[i - nums[j]] - return comb[target] + """Find number of possible combinations in nums that add up to target, in bottom-up manner. + + Keyword arguments: + nums -- positive integer array without duplicates + target -- integer describing what a valid combination should add to + """ + combs = [0] * (target + 1) + combs[0] = 1 + for i in range(0, len(combs)): + for num in nums: + if i - num >= 0: + combs[i] += combs[i - num] + return combs[target] diff --git a/algorithms/dp/edit_distance.py b/algorithms/dp/edit_distance.py index c21c766b7..caf56eedc 100644 --- a/algorithms/dp/edit_distance.py +++ b/algorithms/dp/edit_distance.py @@ -36,28 +36,34 @@ indexes i and j, respectively. To find the edit distance between two words A and B, -we need to find edit(m, n), where m is the length of A and n -is the length of B. +we need to find edit(length_a, length_b). + +Time: O(length_a*length_b) +Space: O(length_a*length_b) """ -def edit_distance(A, B): - # Time: O(m*n) - # Space: O(m*n) +def edit_distance(word_a, word_b): + """Finds edit distance between word_a and word_b + + Kwyword arguments: + word_a -- string + word_b -- string + """ - m, n = len(A) + 1, len(B) + 1 + length_a, length_b = len(word_a) + 1, len(word_b) + 1 - edit = [[0 for _ in range(n)] for _ in range(m)] + edit = [[0 for _ in range(length_b)] for _ in range(length_a)] - for i in range(1, m): + for i in range(1, length_a): edit[i][0] = i - for j in range(1, n): + for j in range(1, length_b): edit[0][j] = j - for i in range(1, m): - for j in range(1, n): - cost = 0 if A[i - 1] == B[j - 1] else 1 + for i in range(1, length_a): + for j in range(1, length_b): + cost = 0 if word_a[i - 1] == word_b[j - 1] else 1 edit[i][j] = min(edit[i - 1][j] + 1, edit[i][j - 1] + 1, edit[i - 1][j - 1] + cost) - return edit[-1][-1] # this is the same as edit[m][n] + return edit[-1][-1] # this is the same as edit[length_a][length_b] diff --git a/algorithms/dp/egg_drop.py b/algorithms/dp/egg_drop.py index b9d38508a..dfa232019 100644 --- a/algorithms/dp/egg_drop.py +++ b/algorithms/dp/egg_drop.py @@ -26,9 +26,14 @@ def egg_drop(n, k): + """ + Keyword arguments: + n -- number of floors + k -- number of eggs + """ # A 2D table where entery eggFloor[i][j] will represent minimum # number of trials needed for i eggs and j floors. - egg_floor = [[0 for x in range(k+1)] for x in range(n + 1)] + egg_floor = [[0 for _ in range(k + 1)] for _ in range(n + 1)] # We need one trial for one floor and 0 trials for 0 floors for i in range(1, n+1): diff --git a/algorithms/dp/fib.py b/algorithms/dp/fib.py index f51020b74..08fa8ea02 100644 --- a/algorithms/dp/fib.py +++ b/algorithms/dp/fib.py @@ -35,8 +35,7 @@ def fib_recursive(n): if n <= 1: return n - else: - return fib_recursive(n-1) + fib_recursive(n-2) + return fib_recursive(n-1) + fib_recursive(n-2) # print(fib_recursive(35)) # => 9227465 (slow) @@ -81,13 +80,13 @@ def fib_iter(n): fib_1 = 0 fib_2 = 1 - sum = 0 + res = 0 if n <= 1: return n for _ in range(n-1): - sum = fib_1 + fib_2 + res = fib_1 + fib_2 fib_1 = fib_2 - fib_2 = sum - return sum + fib_2 = res + return res # print(fib_iter(100)) # => 354224848179261915075 diff --git a/algorithms/dp/hosoya_triangle.py b/algorithms/dp/hosoya_triangle.py index d2e1a8587..a63deaf48 100644 --- a/algorithms/dp/hosoya_triangle.py +++ b/algorithms/dp/hosoya_triangle.py @@ -19,29 +19,38 @@ """ +def hosoya(height, width): + """ Calculates the hosoya triangle -def hosoya(n, m): - if ((n == 0 and m == 0) or (n == 1 and m == 0) or - (n == 1 and m == 1) or (n == 2 and m == 1)): + height -- height of the triangle + """ + if (width == 0) and (height in (0,1)): return 1 - if n > m: - return hosoya(n - 1, m) + hosoya(n - 2, m) - elif m == n: - return hosoya(n - 1, m - 1) + hosoya(n - 2, m - 2) - else: - return 0 - - -def print_hosoya(n): - for i in range(n): + if (width == 1) and (height in (1,2)): + return 1 + if height > width: + return hosoya(height - 1, width) + hosoya(height - 2, width) + if width == height: + return hosoya(height - 1, width - 1) + hosoya(height - 2, width - 2) + return 0 + +def print_hosoya(height): + """Prints the hosoya triangle + + height -- height of the triangle + """ + for i in range(height): for j in range(i + 1): - print(hosoya(i, j), end=" ") - print("\n", end="") + print(hosoya(i, j) , end = " ") + print ("\n", end = "") +def hosoya_testing(height): + """Test hosoya function -def hosoya_testing(n): - x = [] - for i in range(n): + height -- height of the triangle + """ + res = [] + for i in range(height): for j in range(i + 1): - x.append(hosoya(i, j)) - return x + res.append(hosoya(i, j)) + return res diff --git a/algorithms/dp/int_divide.py b/algorithms/dp/int_divide.py index 86ba8cbb6..37c9a44e1 100644 --- a/algorithms/dp/int_divide.py +++ b/algorithms/dp/int_divide.py @@ -1,5 +1,6 @@ """ -Given positive integer n, find an algorithm to find the number of non-negative number division, or descomposition. +Given positive integer decompose, find an algorithm to find the number of +non-negative number division, or decomposition. The complexity is O(n^2). @@ -36,15 +37,19 @@ """ -def int_divide(n): - arr = [[0 for i in range(n + 1)] for j in range(n + 1)] +def int_divide(decompose): + """Find number of decompositions from `decompose` + + decompose -- integer + """ + arr = [[0 for i in range(decompose + 1)] for j in range(decompose + 1)] arr[1][1] = 1 - for i in range(1, n + 1): - for j in range(1, n + 1): + for i in range(1, decompose + 1): + for j in range(1, decompose + 1): if i < j: arr[i][j] = arr[i][i] elif i == j: arr[i][j] = 1 + arr[i][j - 1] else: arr[i][j] = arr[i][j - 1] + arr[i - j][j] - return arr[n][n] + return arr[decompose][decompose] diff --git a/algorithms/dp/job_scheduling.py b/algorithms/dp/job_scheduling.py index f1a92f584..b822c032c 100644 --- a/algorithms/dp/job_scheduling.py +++ b/algorithms/dp/job_scheduling.py @@ -1,65 +1,68 @@ -# Python program for weighted job scheduling using Dynamic -# Programming and Binary Search - -# Class to represent a job - +""" +Python program for weighted job scheduling using Dynamic +Programming and Binary Search +""" class Job: + """ + Class to represent a job + """ def __init__(self, start, finish, profit): self.start = start self.finish = finish - self.profit = profit - + self.profit = profit -# A Binary Search based function to find the latest job -# (before current job) that doesn't conflict with current -# job. "index" is index of the current job. This function -# returns -1 if all jobs before index conflict with it. -# The array jobs[] is sorted in increasing order of finish -# time. def binary_search(job, start_index): - - # Initialize 'lo' and 'hi' for Binary Search - lo = 0 - hi = start_index - 1 + """ + A Binary Search based function to find the latest job + (before current job) that doesn't conflict with current + job. "index" is index of the current job. This function + returns -1 if all jobs before index conflict with it. + The array jobs[] is sorted in increasing order of finish + time. + """ + + left = 0 + right = start_index - 1 # Perform binary Search iteratively - while lo <= hi: - mid = (lo + hi) // 2 + while left <= right: + mid = (left + right) // 2 if job[mid].finish <= job[start_index].start: if job[mid + 1].finish <= job[start_index].start: - lo = mid + 1 + left = mid + 1 else: return mid else: - hi = mid - 1 + right = mid - 1 return -1 - -# The main function that returns the maximum possible -# profit from given array of jobs def schedule(job): + """ + The main function that returns the maximum possible + profit from given array of jobs + """ # Sort jobs according to finish time - job = sorted(job, key=lambda j: j.finish) + job = sorted(job, key = lambda j: j.finish) # Create an array to store solutions of subproblems. table[i] # stores the profit for jobs till arr[i] (including arr[i]) - n = len(job) - table = [0 for _ in range(n)] + length = len(job) + table = [0 for _ in range(length)] table[0] = job[0].profit # Fill entries in table[] using recursive property - for i in range(1, n): + for i in range(1, length): # Find profit including the current job incl_prof = job[i].profit - l = binary_search(job, i) - if (l != -1): - incl_prof += table[l] + pos = binary_search(job, i) + if pos != -1: + incl_prof += table[pos] # Store maximum of including and excluding table[i] = max(incl_prof, table[i - 1]) - return table[n-1] + return table[length-1] diff --git a/algorithms/dp/k_factor.py b/algorithms/dp/k_factor.py index 998d825ee..c072f7541 100644 --- a/algorithms/dp/k_factor.py +++ b/algorithms/dp/k_factor.py @@ -1,78 +1,85 @@ -'''The K factor of a string is defined as the number of -times 'abba' appears as a substring. - -Given two numbers N and k, find the number of strings of -length N with 'K factor' = k. +''' +The K factor of a string is defined as the number of times 'abba' appears as a +substring. Given two numbers `length` and `k_factor`, find the number of +strings of length `length` with 'K factor' = `k_factor`. The algorithms is as follows: -dp[n][k] will be a 4 element array, wherein each element can be -the number of strings of length n and 'K factor' = k which -belong to the criteria represented by that index: +dp[length][k_factor] will be a 4 element array, wherein each element can be the +number of strings of length `length` and 'K factor' = `k_factor` which belong +to the criteria represented by that index: + + - dp[length][k_factor][0] can be the number of strings of length `length` + and K-factor = `k_factor` which end with substring 'a' + + - dp[length][k_factor][1] can be the number of strings of length `length` + and K-factor = `k_factor` which end with substring 'ab' + + - dp[length][k_factor][2] can be the number of strings of length `length` + and K-factor = `k_factor` which end with substring 'abb' - dp[n][k][0] can be the number of strings of length n and K-factor = k - which end with substring 'a' - dp[n][k][1] can be the number of strings of length n and K-factor = k - which end with substring 'ab' - dp[n][k][2] can be the number of strings of length n and K-factor = k - which end with substring 'abb' - dp[n][k][3] can be the number of strings of length n and K-factor = k - which end with anything other than the above substrings (anything other - than 'a' 'ab' 'abb') + - dp[length][k_factor][3] can be the number of strings of `length` and + K-factor = `k_factor` which end with anything other than the above + substrings (anything other than 'a' 'ab' 'abb') Example inputs -n=4 k=1 no of strings = 1 -n=7 k=1 no of strings = 70302 -n=10 k=2 no of strings = 74357 +length=4 k_factor=1 no of strings = 1 +length=7 k_factor=1 no of strings = 70302 +length=10 k_factor=2 no of strings = 74357 ''' +def find_k_factor(length, k_factor): + """Find the number of strings of length `length` with K factor = `k_factor`. -def find_k_factor(n, k): - dp = [[[0 for i in range(4)]for j in range((n-1)//3+2)]for k in range(n+1)] - if(3*k+1 > n): + Keyword arguments: + length -- integer + k_factor -- integer + """ + mat=[[[0 for i in range(4)]for j in range((length-1)//3+2)]for k in range(length+1)] + if 3*k_factor+1>length: return 0 - # base cases - dp[1][0][0] = 1 - dp[1][0][1] = 0 - dp[1][0][2] = 0 - dp[1][0][3] = 25 - - for i in range(2, n+1): - for j in range((n-1)//3+2): - if(j == 0): - # adding a at the end - dp[i][j][0] = dp[i-1][j][0] + dp[i-1][j][1] + dp[i-1][j][3] - - # adding b at the end - dp[i][j][1] = dp[i-1][j][0] - dp[i][j][2] = dp[i-1][j][1] - - # adding any other lowercase character - dp[i][j][3] = dp[i-1][j][0]*24+dp[i-1][j][1]*24+dp[i-1][j][2]*25+dp[i-1][j][3]*25 - - elif(3*j+1 < i): - # adding a at the end - dp[i][j][0] = dp[i-1][j][0]+dp[i-1][j][1]+dp[i-1][j][3]+dp[i-1][j-1][2] - - # adding b at the end - dp[i][j][1] = dp[i-1][j][0] - dp[i][j][2] = dp[i-1][j][1] - - # adding any other lowercase character - dp[i][j][3] = dp[i-1][j][0]*24+dp[i-1][j][1]*24+dp[i-1][j][2]*25+dp[i-1][j][3]*25 - - elif(3*j+1 == i): - dp[i][j][0] = 1 - dp[i][j][1] = 0 - dp[i][j][2] = 0 - dp[i][j][3] = 0 + #base cases + mat[1][0][0]=1 + mat[1][0][1]=0 + mat[1][0][2]=0 + mat[1][0][3]=25 + + for i in range(2,length+1): + for j in range((length-1)//3+2): + if j==0: + #adding a at the end + mat[i][j][0]=mat[i-1][j][0]+mat[i-1][j][1]+mat[i-1][j][3] + + #adding b at the end + mat[i][j][1]=mat[i-1][j][0] + mat[i][j][2]=mat[i-1][j][1] + + #adding any other lowercase character + mat[i][j][3]=mat[i-1][j][0]*24+mat[i-1][j][1]*24+mat[i-1][j][2]*25+mat[i-1][j][3]*25 + + elif 3*j+1> 1 - if i <= mid: - update(p << 1, l, mid, i, v) + mid = (left+right)>>1 + if target <= mid: + update(pos<<1, left, mid, target, vertex) else: - update((p << 1) | 1, mid + 1, r, i, v) - tree[p] = max(tree[p << 1], tree[(p << 1) | 1]) + update((pos<<1)|1, mid+1, right, target, vertex) + tree[pos] = max_seq(tree[pos<<1], tree[(pos<<1)|1]) - def get_max(p, l, r, s, e): - if l > e or r < s: + def get_max(pos, left, right, start, end): + if left > end or right < start: return 0 - if l >= s and r <= e: - return tree[p] - mid = (l+r) >> 1 - return max(get_max(p << 1, l, mid, s, e), get_max((p << 1) | 1, mid+1, r, s, e)) + if left >= start and right <= end: + return tree[pos] + mid = (left+right)>>1 + return max_seq(get_max(pos<<1, left, mid, start, end), + get_max((pos<<1)|1, mid+1, right, start, end)) ans = 0 - for x in sequence: - cur = get_max(1, 0, mx, 0, x-1)+1 - ans = max(ans, cur) - update(1, 0, mx, x, cur) + for element in sequence: + cur = get_max(1, 0, max_seq, 0, element-1)+1 + ans = max_seq(ans, cur) + update(1, 0, max_seq, element, cur) return ans @@ -85,32 +86,32 @@ def longest_increasing_subsequence_optimized2(sequence): type sequence: list[int] rtype: int """ - n = len(sequence) - tree = [0] * (n << 2) + length = len(sequence) + tree = [0] * (length<<2) sorted_seq = sorted((x, -i) for i, x in enumerate(sequence)) - - def update(p, l, r, i, v): - if l == r: - tree[p] = v + def update(pos, left, right, target, vertex): + if left == right: + tree[pos] = vertex return - mid = (l+r) >> 1 - if i <= mid: - update(p << 1, l, mid, i, v) + mid = (left+right)>>1 + if target <= mid: + vertex(pos<<1, left, mid, target, vertex) else: - update((p << 1) | 1, mid+1, r, i, v) - tree[p] = max(tree[p << 1], tree[(p << 1) | 1]) + vertex((pos<<1)|1, mid+1, right, target, vertex) + tree[pos] = max(tree[pos<<1], tree[(pos<<1)|1]) - def get_max(p, l, r, s, e): - if l > e or r < s: + def get_max(pos, left, right, start, end): + if left > end or right < start: return 0 - if l >= s and r <= e: - return tree[p] - mid = (l+r) >> 1 - return max(get_max(p << 1, l, mid, s, e), get_max((p << 1) | 1, mid+1, r, s, e)) + if left >= start and right <= end: + return tree[pos] + mid = (left+right)>>1 + return max(get_max(pos<<1, left, mid, start, end), + get_max((pos<<1)|1, mid+1, right, start, end)) ans = 0 - for x, j in sorted_seq: - i = -j - cur = get_max(1, 0, n-1, 0, i-1)+1 + for tup in sorted_seq: + i = -tup[1] + cur = get_max(1, 0, length-1, 0, i-1)+1 ans = max(ans, cur) - update(1, 0, n-1, i, cur) + update(1, 0, length-1, i, cur) return ans diff --git a/algorithms/dp/matrix_chain_order.py b/algorithms/dp/matrix_chain_order.py index 47386f66f..dd2ef7bd9 100644 --- a/algorithms/dp/matrix_chain_order.py +++ b/algorithms/dp/matrix_chain_order.py @@ -8,6 +8,10 @@ def matrix_chain_order(array): + """Finds optimal order to multiply matrices + + array -- int[] + """ 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)] @@ -24,10 +28,15 @@ def matrix_chain_order(array): return matrix, sol # Print order of matrix with Ai as matrix +def print_optimal_solution(optimal_solution,i,j): + """Print the solution -def print_optimal_solution(optimal_solution, i, j): - if i == j: - print("A" + str(i), end=" ") + optimal_solution -- int[][] + i -- int[] + j -- int[] + """ + if i==j: + print("A" + str(i),end = " ") else: print("(", end=" ") print_optimal_solution(optimal_solution, i, optimal_solution[i][j]) @@ -36,15 +45,17 @@ def print_optimal_solution(optimal_solution, i, j): def main(): - array = [30, 35, 15, 5, 10, 20, 25] - n = len(array) - # Size of matrix created from above array will be + """ + Testing for matrix_chain_ordering + """ + array=[30,35,15,5,10,20,25] + length=len(array) + #Size of matrix created from above array will be # 30*35 35*15 15*5 5*10 10*20 20*25 matrix, optimal_solution = matrix_chain_order(array) - print("No. of Operation required: "+str((matrix[1][n-1]))) - print_optimal_solution(optimal_solution, 1, n-1) - + print("No. of Operation required: "+str((matrix[1][length-1]))) + print_optimal_solution(optimal_solution,1,length-1) if __name__ == '__main__': main() diff --git a/algorithms/dp/max_product_subarray.py b/algorithms/dp/max_product_subarray.py index 592e98fb1..7a9beac63 100644 --- a/algorithms/dp/max_product_subarray.py +++ b/algorithms/dp/max_product_subarray.py @@ -14,15 +14,15 @@ def max_product(nums): :rtype: int """ lmin = lmax = gmax = nums[0] - for i in range(len(nums)): - t1 = nums[i] * lmax - t2 = nums[i] * lmin - lmax = max(max(t1, t2), nums[i]) - lmin = min(min(t1, t2), nums[i]) + for num in nums: + t_1 = num * lmax + t_2 = num * lmin + lmax = max(max(t_1, t_2), num) + lmin = min(min(t_1, t_2), num) gmax = max(gmax, lmax) -''' +""" Another approach that would print max product and the subarray Examples: @@ -34,18 +34,18 @@ def max_product(nums): #=> max_product_so_far: 24, [-4, -3, -2, -1] subarray_with_max_product([-3,0,1]) #=> max_product_so_far: 1, [1] -''' +""" def subarray_with_max_product(arr): ''' arr is list of positive/negative numbers ''' - l = len(arr) + length = len(arr) product_so_far = max_product_end = 1 max_start_i = 0 so_far_start_i = so_far_end_i = 0 all_negative_flag = True - for i in range(l): + for i in range(length): max_product_end *= arr[i] if arr[i] > 0: all_negative_flag = False @@ -60,8 +60,7 @@ def subarray_with_max_product(arr): so_far_start_i = max_start_i if all_negative_flag: - print("max_product_so_far: %s, %s" % - (reduce(lambda x, y: x * y, arr), arr)) + print(f"max_product_so_far: {reduce(lambda x, y: x * y, arr)}, {arr}") + else: - print("max_product_so_far: %s, %s" % - (product_so_far, arr[so_far_start_i:so_far_end_i + 1])) + print(f"max_product_so_far: {product_so_far},{arr[so_far_start_i:so_far_end_i + 1]}") diff --git a/algorithms/dp/min_cost_path.py b/algorithms/dp/min_cost_path.py index 356489e9a..7771e1e0f 100644 --- a/algorithms/dp/min_cost_path.py +++ b/algorithms/dp/min_cost_path.py @@ -27,29 +27,32 @@ def min_cost(cost): + """Find minimum cost. - n = len(cost) + Keyword arguments: + cost -- matrix containing costs + """ + length = len(cost) # dist[i] stores minimum cost from 0 --> i. - dist = [INF] * n + dist = [INF] * length dist[0] = 0 # cost from 0 --> 0 is zero. - for i in range(n): - for j in range(i+1, n): + for i in range(length): + for j in range(i+1,length): dist[j] = min(dist[j], dist[i] + cost[i][j]) - return dist[n-1] + return dist[length-1] if __name__ == '__main__': + costs = [ [ 0, 15, 80, 90], # cost[i][j] is the cost of + [-1, 0, 40, 50], # going from i --> j + [-1, -1, 0, 70], + [-1, -1, -1, 0] ] # cost[i][j] = -1 for i > j + TOTAL_LEN = len(costs) - cost = [[0, 15, 80, 90], # cost[i][j] is the cost of - [-1, 0, 40, 50], # going from i --> j - [-1, -1, 0, 70], - [-1, -1, -1, 0]] # cost[i][j] = -1 for i > j - total_len = len(cost) - - mcost = min_cost(cost) + mcost = min_cost(costs) assert mcost == 65 - print("The Minimum cost to reach station %d is %d" % (total_len, mcost)) + print(f"The minimum cost to reach station {TOTAL_LEN} is {mcost}") diff --git a/algorithms/dp/num_decodings.py b/algorithms/dp/num_decodings.py index 541db6edf..87cd3e3dc 100644 --- a/algorithms/dp/num_decodings.py +++ b/algorithms/dp/num_decodings.py @@ -17,33 +17,37 @@ """ -def num_decodings(s): +def num_decodings(enc_mes): """ :type s: str :rtype: int """ - if not s or s[0] == "0": + if not enc_mes or enc_mes[0] == "0": return 0 - wo_last, wo_last_two = 1, 1 - for i in range(1, len(s)): - x = wo_last if s[i] != "0" else 0 - y = wo_last_two if int(s[i-1:i+1]) < 27 and s[i-1] != "0" else 0 - wo_last_two = wo_last - wo_last = x+y - return wo_last + last_char, last_two_chars = 1, 1 + for i in range(1, len(enc_mes)): + last = last_char if enc_mes[i] != "0" else 0 + last_two = last_two_chars if int(enc_mes[i-1:i+1]) < 27 and enc_mes[i-1] != "0" else 0 + last_two_chars = last_char + last_char = last+last_two + return last_char -def num_decodings2(s): - if not s or s.startswith('0'): +def num_decodings2(enc_mes): + """ + :type s: str + :rtype: int + """ + if not enc_mes or enc_mes.startswith('0'): return 0 stack = [1, 1] - for i in range(1, len(s)): - if s[i] == '0': - if s[i-1] == '0' or s[i-1] > '2': + for i in range(1, len(enc_mes)): + if enc_mes[i] == '0': + if enc_mes[i-1] == '0' or enc_mes[i-1] > '2': # only '10', '20' is valid return 0 stack.append(stack[-2]) - elif 9 < int(s[i-1:i+1]) < 27: + elif 9 < int(enc_mes[i-1:i+1]) < 27: # '01 - 09' is not allowed stack.append(stack[-2]+stack[-1]) else: diff --git a/algorithms/dp/planting_trees.py b/algorithms/dp/planting_trees.py index 56b99cde7..ee8394b8e 100644 --- a/algorithms/dp/planting_trees.py +++ b/algorithms/dp/planting_trees.py @@ -1,26 +1,25 @@ """ -An even number of trees are left along one side of a country road. -You've been assigned the job to plant these trees at an even interval on -both sides of the road. The length L and width W of the road are variable, -and a pair of trees must be planted at the beginning (at 0) -and at the end (at L) of the road. Only one tree can be moved at a time. -The goal is to calculate the lowest amount of distance that the trees -have to be moved before they are all in a valid position. +An even number of trees are left along one side of a country road. You've been +assigned the job to plant these trees at an even interval on both sides of the +road. The length and width of the road are variable, and a pair of trees must +be planted at the beginning (at 0) and at the end (at length) of the road. Only +one tree can be moved at a time. The goal is to calculate the lowest amount of +distance that the trees have to be moved before they are all in a valid +position. """ from math import sqrt - -def planting_trees(trees, L, W): +def planting_trees(trees, length, width): """ Returns the minimum distance that trees have to be moved before they are all in a valid state. Parameters: tree (list): A sorted list of integers with all trees' - position along the road. - L (int): An integer with the length of the road. - W (int): An integer with the width of the road. + position along the road. + length (int): An integer with the length of the road. + width (int): An integer with the width of the road. Returns: A float number with the total distance trees have been moved. @@ -29,21 +28,22 @@ def planting_trees(trees, L, W): n_pairs = int(len(trees)/2) - space_between_pairs = L/(n_pairs-1) + space_between_pairs = length/(n_pairs-1) target_locations = [location*space_between_pairs for location in range(n_pairs)] cmatrix = [[0 for _ in range(n_pairs+1)] for _ in range(n_pairs+1)] - for ri in range(1, n_pairs+1): - cmatrix[ri][0] = cmatrix[ri-1][0] + sqrt(W + abs(trees[ri]-target_locations[ri-1])**2) - for li in range(1, n_pairs+1): - cmatrix[0][li] = cmatrix[0][li-1] + abs(trees[li]-target_locations[li-1]) - - for ri in range(1, n_pairs+1): - for li in range(1, n_pairs+1): - cmatrix[ri][li] = min( - cmatrix[ri-1][li] + sqrt(W + (trees[li + ri]-target_locations[ri-1])**2), - cmatrix[ri][li-1] + abs(trees[li + ri]-target_locations[li-1]) + for r_i in range(1, n_pairs+1): + cmatrix[r_i][0] = cmatrix[r_i-1][0] + sqrt( + width + abs(trees[r_i]-target_locations[r_i-1])**2) + for l_i in range(1, n_pairs+1): + cmatrix[0][l_i] = cmatrix[0][l_i-1] + abs(trees[l_i]-target_locations[l_i-1]) + + for r_i in range(1, n_pairs+1): + for l_i in range(1, n_pairs+1): + cmatrix[r_i][l_i] = min( + cmatrix[r_i-1][l_i] + sqrt(width + (trees[l_i + r_i]-target_locations[r_i-1])**2), + cmatrix[r_i][l_i-1] + abs(trees[l_i + r_i]-target_locations[l_i-1]) ) return cmatrix[n_pairs][n_pairs] diff --git a/algorithms/dp/regex_matching.py b/algorithms/dp/regex_matching.py index 72311d557..3b65e3594 100644 --- a/algorithms/dp/regex_matching.py +++ b/algorithms/dp/regex_matching.py @@ -18,91 +18,44 @@ is_match("ab", ".*") → true is_match("aab", "c*a*b") → true """ -import unittest -class Solution(object): - def is_match(self, s, p): - m, n = len(s) + 1, len(p) + 1 - matches = [[False] * n for _ in range(m)] - - # Match empty string with empty pattern - matches[0][0] = True - - # Match empty string with .* - for i, element in enumerate(p[1:], 2): - matches[0][i] = matches[0][i - 2] and element == '*' - - for i, ss in enumerate(s, 1): - for j, pp in enumerate(p, 1): - if pp != '*': - # The previous character has matched and the current one - # has to be matched. Two possible matches: the same or . - matches[i][j] = matches[i - 1][j - 1] and \ - (ss == pp or pp == '.') - else: - # Horizontal look up [j - 2]. - # Not use the character before *. - matches[i][j] |= matches[i][j - 2] - - # Vertical look up [i - 1]. - # Use at least one character before *. - # p a b * - # s 1 0 0 0 - # a 0 1 0 1 - # b 0 0 1 1 - # b 0 0 0 ? - if ss == p[j - 2] or p[j - 2] == '.': - matches[i][j] |= matches[i - 1][j] - - return matches[-1][-1] - -class TestSolution(unittest.TestCase): - def test_none_0(self): - s = "" - p = "" - self.assertTrue(Solution().is_match(s, p)) - - def test_none_1(self): - s = "" - p = "a" - self.assertFalse(Solution().is_match(s, p)) - - def test_no_symbol_equal(self): - s = "abcd" - p = "abcd" - self.assertTrue(Solution().is_match(s, p)) - - def test_no_symbol_not_equal_0(self): - s = "abcd" - p = "efgh" - self.assertFalse(Solution().is_match(s, p)) - - def test_no_symbol_not_equal_1(self): - s = "ab" - p = "abb" - self.assertFalse(Solution().is_match(s, p)) - - def test_symbol_0(self): - s = "" - p = "a*" - self.assertTrue(Solution().is_match(s, p)) - - def test_symbol_1(self): - s = "a" - p = "ab*" - self.assertTrue(Solution().is_match(s, p)) - - def test_symbol_2(self): - # E.g. - # s a b b - # p 1 0 0 0 - # a 0 1 0 0 - # b 0 0 1 0 - # * 0 1 1 1 - s = "abb" - p = "ab*" - self.assertTrue(Solution().is_match(s, p)) - - -if __name__ == "__main__": - unittest.main() +def is_match(str_a, str_b): + """Finds if `str_a` matches `str_b` + + Keyword arguments: + str_a -- string + str_b -- string + """ + len_a, len_b = len(str_a) + 1, len(str_b) + 1 + matches = [[False] * len_b for _ in range(len_a)] + + # Match empty string with empty pattern + matches[0][0] = True + + # Match empty string with .* + for i, element in enumerate(str_b[1:], 2): + matches[0][i] = matches[0][i - 2] and element == '*' + + for i, char_a in enumerate(str_a, 1): + for j, char_b in enumerate(str_b, 1): + if char_b != '*': + # The previous character has matched and the current one + # has to be matched. Two possible matches: the same or . + matches[i][j] = matches[i - 1][j - 1] and \ + char_b in (char_a, '.') + else: + # Horizontal look up [j - 2]. + # Not use the character before *. + matches[i][j] |= matches[i][j - 2] + + # Vertical look up [i - 1]. + # Use at least one character before *. + # p a b * + # s 1 0 0 0 + # a 0 1 0 1 + # b 0 0 1 1 + # b 0 0 0 ? + if char_a == str_b[j - 2] or str_b[j - 2] == '.': + matches[i][j] |= matches[i - 1][j] + + return matches[-1][-1] diff --git a/algorithms/dp/rod_cut.py b/algorithms/dp/rod_cut.py index b4a82245e..d9259e862 100644 --- a/algorithms/dp/rod_cut.py +++ b/algorithms/dp/rod_cut.py @@ -1,24 +1,28 @@ -# A Dynamic Programming solution for Rod cutting problem +"""A Dynamic Programming solution for Rod cutting problem +""" + INT_MIN = -32767 - -# Returns the best obtainable price for a rod of length n and -# price[] as prices of different pieces + def cut_rod(price): + """ + Returns the best obtainable price for a rod of length n and + price[] as prices of different pieces + """ n = len(price) val = [0]*(n+1) - + # Build the table val[] in bottom up manner and return # the last entry from the table for i in range(1, n+1): max_val = INT_MIN for j in range(i): - max_val = max(max_val, price[j] + val[i-j-1]) + max_val = max(max_val, price[j] + val[i-j-1]) val[i] = max_val - + return val[n] - + # Driver program to test above functions arr = [1, 5, 8, 9, 10, 17, 17, 20] print("Maximum Obtainable Value is " + str(cut_rod(arr))) - + # This code is contributed by Bhavya Jain diff --git a/algorithms/dp/word_break.py b/algorithms/dp/word_break.py index ea244dc68..f520456b0 100644 --- a/algorithms/dp/word_break.py +++ b/algorithms/dp/word_break.py @@ -1,44 +1,41 @@ """ Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, -determine if s can be segmented into a space-separated +determine if word can be segmented into a space-separated sequence of one or more dictionary words. You may assume the dictionary does not contain duplicate words. For example, given -s = "leetcode", +word = "leetcode", dict = ["leet", "code"]. Return true because "leetcode" can be segmented as "leet code". -""" - -""" -s = abc word_dict = ["a","bc"] +word = abc word_dict = ["a","bc"] True False False False """ # TC: O(N^2) SC: O(N) -def word_break(s, word_dict): +def word_break(word, word_dict): """ - :type s: str + :type word: str :type word_dict: Set[str] :rtype: bool """ - dp = [False] * (len(s)+1) - dp[0] = True - for i in range(1, len(s)+1): + dp_array = [False] * (len(word)+1) + dp_array[0] = True + for i in range(1, len(word)+1): for j in range(0, i): - if dp[j] and s[j:i] in word_dict: - dp[i] = True + if dp_array[j] and word[j:i] in word_dict: + dp_array[i] = True break - return dp[-1] + return dp_array[-1] if __name__ == "__main__": - s = "keonkim" + STR = "keonkim" dic = ["keon", "kim"] - print(word_break(s, dic)) + print(word_break(str, dic)) diff --git a/algorithms/graph/__init__.py b/algorithms/graph/__init__.py index 3bf3a1c8c..1f52f9bdb 100644 --- a/algorithms/graph/__init__.py +++ b/algorithms/graph/__init__.py @@ -1,3 +1,7 @@ +""" +Collection of algorithms on graphs. +""" + from .tarjan import * from .check_bipartite import * from .maximum_flow import * diff --git a/algorithms/graph/all_pairs_shortest_path.py b/algorithms/graph/all_pairs_shortest_path.py index 46f3b7c09..d8e6feca3 100644 --- a/algorithms/graph/all_pairs_shortest_path.py +++ b/algorithms/graph/all_pairs_shortest_path.py @@ -7,22 +7,36 @@ example -a = [[0, 0.1, 0.101, 0.142, 0.277], [0.465, 0, 0.191, 0.192, 0.587], [0.245, 0.554, 0, 0.333, 0.931], [1.032, 0.668, 0.656, 0, 0.151], [0.867, 0.119, 0.352, 0.398, 0]] +a = [[0 , 0.1 , 0.101, 0.142, 0.277], + [0.465, 0 , 0.191, 0.192, 0.587], + [0.245, 0.554, 0 , 0.333, 0.931], + [1.032, 0.668, 0.656, 0 , 0.151], + [0.867, 0.119, 0.352, 0.398, 0]] -result +result -[[0, 0.1, 0.101, 0.142, 0.277], [0.436, 0, 0.191, 0.192, 0.34299999999999997], [0.245, 0.345, 0, 0.333, 0.484], [0.706, 0.27, 0.46099999999999997, 0, 0.151], [0.5549999999999999, 0.119, 0.31, 0.311, 0]] +[[0 , 0.1 , 0.101, 0.142, 0.277], + [0.436, 0 , 0.191, 0.192, 0.343], + [0.245, 0.345, 0 , 0.333, 0.484], + [0.706, 0.27 , 0.461, 0 , 0.151], + [0.555, 0.119, 0.31 , 0.311, 0]] """ import copy def all_pairs_shortest_path(adjacency_matrix): + """ + Given a matrix of the edge weights between respective nodes, returns a + matrix containing the shortest distance distance between the two nodes. + """ + new_array = copy.deepcopy(adjacency_matrix) - for k in range(len(new_array)): - for i in range(len(new_array)): - for j in range(len(new_array)): + size = len(new_array) + for k in range(size): + for i in range(size): + for j in range(size): if new_array[i][j] > new_array[i][k] + new_array[k][j]: - new_array[i][j] = new_array[i][k] + new_array[k][j] - - return new_array \ No newline at end of file + new_array[i][j] = new_array[i][k] + new_array[k][j] + + return new_array diff --git a/algorithms/graph/bellman_ford.py b/algorithms/graph/bellman_ford.py index 518f75c4b..ea1e0465b 100644 --- a/algorithms/graph/bellman_ford.py +++ b/algorithms/graph/bellman_ford.py @@ -1,26 +1,29 @@ -''' -This Bellman-Ford Code is for determination whether we can get -shortest path from given graph or not for single-source shortest-paths problem. -In other words, if given graph has any negative-weight cycle that is reachable -from the source, then it will give answer False for "no solution exits". -For argument graph, it should be a dictionary type -such as -graph = { - 'a': {'b': 6, 'e': 7}, - 'b': {'c': 5, 'd': -4, 'e': 8}, - 'c': {'b': -2}, - 'd': {'a': 2, 'c': 7}, - 'e': {'b': -3} -} -''' +""" +Determination of single-source shortest-path. +""" def bellman_ford(graph, source): + """ + This Bellman-Ford Code is for determination whether we can get + shortest path from given graph or not for single-source shortest-paths problem. + In other words, if given graph has any negative-weight cycle that is reachable + from the source, then it will give answer False for "no solution exits". + For argument graph, it should be a dictionary type + such as + graph = { + 'a': {'b': 6, 'e': 7}, + 'b': {'c': 5, 'd': -4, 'e': 8}, + 'c': {'b': -2}, + 'd': {'a': 2, 'c': 7}, + 'e': {'b': -3} + } + """ weight = {} pre_node = {} initialize_single_source(graph, source, weight, pre_node) - for i in range(1, len(graph)): + for _ in range(1, len(graph)): for node in graph: for adjacent in graph[node]: if weight[adjacent] > weight[node] + graph[node][adjacent]: @@ -35,10 +38,11 @@ def bellman_ford(graph, source): return True def initialize_single_source(graph, source, weight, pre_node): - + """ + Initialize data structures for Bellman-Ford algorithm. + """ for node in graph: weight[node] = float('inf') pre_node[node] = None weight[source] = 0 - diff --git a/algorithms/graph/check_bipartite.py b/algorithms/graph/check_bipartite.py index dacc003b3..51a84ab4c 100644 --- a/algorithms/graph/check_bipartite.py +++ b/algorithms/graph/check_bipartite.py @@ -1,39 +1,40 @@ """ - Bipartite graph is a graph whose vertices can be divided into two disjoint and independent sets. (https://en.wikipedia.org/wiki/Bipartite_graph) - -Time complexity is O(|E|) -Space complexity is O(|V|) - """ def check_bipartite(adj_list): + """ + Determine if the given graph is bipartite. - V = len(adj_list) + Time complexity is O(|E|) + Space complexity is O(|V|) + """ - # Divide vertexes in the graph into set_type 1 and 2 + vertices = len(adj_list) + + # Divide vertexes in the graph into set_type 0 and 1 # Initialize all set_types as -1 - set_type = [-1 for v in range(V)] + set_type = [-1 for v in range(vertices)] set_type[0] = 0 - q = [0] + queue = [0] - while q: - v = q.pop(0) + while queue: + current = queue.pop(0) # If there is a self-loop, it cannot be bipartite - if adj_list[v][v]: + if adj_list[current][current]: return False - for u in range(V): - if adj_list[v][u]: - if set_type[u] == set_type[v]: + for adjacent in range(vertices): + if adj_list[current][adjacent]: + if set_type[adjacent] == set_type[current]: return False - elif set_type[u] == -1: + + if set_type[adjacent] == -1: # set type of u opposite of v - set_type[u] = 1 - set_type[v] - q.append(u) + set_type[adjacent] = 1 - set_type[current] + queue.append(adjacent) return True - diff --git a/algorithms/graph/check_digraph_strongly_connected.py b/algorithms/graph/check_digraph_strongly_connected.py index 25ae80cd2..03dd6ab79 100644 --- a/algorithms/graph/check_digraph_strongly_connected.py +++ b/algorithms/graph/check_digraph_strongly_connected.py @@ -1,53 +1,69 @@ +""" +In a directed graph, a strongly connected component is a set of vertices such +that for any pairs of vertices u and v there exists a path (u-...-v) that +connects them. A graph is strongly connected if it is a single strongly +connected component. +""" + from collections import defaultdict class Graph: - def __init__(self,v): - self.v = v - self.graph = defaultdict(list) - - def add_edge(self,u,v): - self.graph[u].append(v) - - def dfs(self): - visited = [False] * self.v - self.dfs_util(0,visited) - if visited == [True]*self.v: - return True - return False - - def dfs_util(self,i,visited): - visited[i] = True - for u in self.graph[i]: - if not(visited[u]): - self.dfs_util(u,visited) - - def reverse_graph(self): - g = Graph(self.v) - for i in range(len(self.graph)): - for j in self.graph[i]: - g.add_edge(j,i) - return g - - - def is_sc(self): - if self.dfs(): - gr = self.reverse_graph() - if gr.dfs(): - return True - return False - - -g1 = Graph(5) -g1.add_edge(0, 1) -g1.add_edge(1, 2) -g1.add_edge(2, 3) -g1.add_edge(3, 0) -g1.add_edge(2, 4) -g1.add_edge(4, 2) -print ("Yes") if g1.is_sc() else print("No") - -g2 = Graph(4) -g2.add_edge(0, 1) -g2.add_edge(1, 2) -g2.add_edge(2, 3) -print ("Yes") if g2.is_sc() else print("No") + """ + A directed graph where edges are one-way (a two-way edge can be represented by using two edges). + """ + + def __init__(self,vertex_count): + """ + Create a new graph with vertex_count vertices. + """ + + self.vertex_count = vertex_count + self.graph = defaultdict(list) + + def add_edge(self,source,target): + """ + Add an edge going from source to target + """ + self.graph[source].append(target) + + def dfs(self): + """ + Determine if all nodes are reachable from node 0 + """ + visited = [False] * self.vertex_count + self.dfs_util(0,visited) + if visited == [True]*self.vertex_count: + return True + return False + + def dfs_util(self,source,visited): + """ + Determine if all nodes are reachable from the given node + """ + visited[source] = True + for adjacent in self.graph[source]: + if not visited[adjacent]: + self.dfs_util(adjacent,visited) + + def reverse_graph(self): + """ + Create a new graph where every edge a->b is replaced with an edge b->a + """ + reverse_graph = Graph(self.vertex_count) + for source, adjacent in self.graph.items(): + for target in adjacent: + # Note: we reverse the order of arguments + # pylint: disable=arguments-out-of-order + reverse_graph.add_edge(target,source) + return reverse_graph + + + def is_strongly_connected(self): + """ + Determine if the graph is strongly connected. + """ + if self.dfs(): + reversed_graph = self.reverse_graph() + if reversed_graph.dfs(): + return True + return False diff --git a/algorithms/graph/clone_graph.py b/algorithms/graph/clone_graph.py index 0fbae1d43..84d0324cf 100644 --- a/algorithms/graph/clone_graph.py +++ b/algorithms/graph/clone_graph.py @@ -1,4 +1,4 @@ -""" +r""" Clone an undirected graph. Each node in the graph contains a label and a list of its neighbors. @@ -29,69 +29,95 @@ import collections -# Definition for a undirected graph node class UndirectedGraphNode: - def __init__(self, x): - self.label = x + """ + A node in an undirected graph. Contains a label and a list of neighbouring + nodes (initially empty). + """ + + def __init__(self, label): + self.label = label self.neighbors = [] + def shallow_copy(self): + """ + Return a shallow copy of this node (ignoring any neighbors) + """ + return UndirectedGraphNode(self.label) + + def add_neighbor(self, node): + """ + Adds a new neighbor + """ + self.neighbors.append(node) + -# BFS def clone_graph1(node): + """ + Returns a new graph as seen from the given node using a breadth first search (BFS). + """ if not node: - return - node_copy = UndirectedGraphNode(node.label) + return None + node_copy = node.shallow_copy() dic = {node: node_copy} queue = collections.deque([node]) while queue: node = queue.popleft() for neighbor in node.neighbors: if neighbor not in dic: # neighbor is not visited - neighbor_copy = UndirectedGraphNode(neighbor.label) + neighbor_copy = neighbor.shallow_copy() dic[neighbor] = neighbor_copy - dic[node].neighbors.append(neighbor_copy) + dic[node].add_neighbor(neighbor_copy) queue.append(neighbor) else: - dic[node].neighbors.append(dic[neighbor]) + dic[node].add_neighbor(dic[neighbor]) return node_copy -# DFS iteratively def clone_graph2(node): + """ + Returns a new graph as seen from the given node using an iterative depth first search (DFS). + """ if not node: - return - node_copy = UndirectedGraphNode(node.label) + return None + node_copy = node.shallow_copy() dic = {node: node_copy} stack = [node] while stack: node = stack.pop() for neighbor in node.neighbors: if neighbor not in dic: - neighbor_copy = UndirectedGraphNode(neighbor.label) + neighbor_copy = neighbor.shallow_copy() dic[neighbor] = neighbor_copy - dic[node].neighbors.append(neighbor_copy) + dic[node].add_neighbor(neighbor_copy) stack.append(neighbor) else: - dic[node].neighbors.append(dic[neighbor]) + dic[node].add_neighbor(dic[neighbor]) return node_copy -# DFS recursively def clone_graph(node): + """ + Returns a new graph as seen from the given node using a recursive depth first search (DFS). + """ if not node: - return - node_copy = UndirectedGraphNode(node.label) + return None + node_copy = node.shallow_copy() dic = {node: node_copy} dfs(node, dic) return node_copy def dfs(node, dic): + """ + Clones a graph using a recursive depth first search. Stores the clones in + the dictionary, keyed by the original nodes. + """ for neighbor in node.neighbors: if neighbor not in dic: - neighbor_copy = UndirectedGraphNode(neighbor.label) + neighbor_copy = neighbor.shallow_copy() dic[neighbor] = neighbor_copy - dic[node].neighbors.append(neighbor_copy) + dic[node].add_neighbor(neighbor_copy) dfs(neighbor, dic) else: - dic[node].neighbors.append(dic[neighbor]) + dic[node].add_neighbor(dic[neighbor]) diff --git a/algorithms/graph/count_connected_number_of_component.py b/algorithms/graph/count_connected_number_of_component.py index a944cf096..a58eda6c3 100644 --- a/algorithms/graph/count_connected_number_of_component.py +++ b/algorithms/graph/count_connected_number_of_component.py @@ -1,7 +1,7 @@ #count connected no of component using DFS ''' -In graph theory, a component, sometimes called a connected component, -of an undirected graph is a subgraph in which any +In graph theory, a component, sometimes called a connected component, +of an undirected graph is a subgraph in which any two vertices are connected to each other by paths. Example: @@ -19,16 +19,16 @@ # Code is Here -def dfs(source,visited,l): +def dfs(source,visited,adjacency_list): ''' Function that performs DFS ''' visited[source] = True - for child in l[source]: + for child in adjacency_list[source]: if not visited[child]: - dfs(child,visited,l) - -def count_components(l,size): - ''' + dfs(child,visited,adjacency_list) + +def count_components(adjacency_list,size): + ''' Function that counts the Connected components on bases of DFS. return type : int ''' @@ -37,18 +37,23 @@ def count_components(l,size): visited = [False]*(size+1) for i in range(1,size+1): if not visited[i]: - dfs(i,visited,l) + dfs(i,visited,adjacency_list) count+=1 - return count + return count + +def main(): + """ + Example application + """ + node_count,edge_count = map(int, input("Enter the Number of Nodes and Edges \n").split(' ')) + adjacency = [[] for _ in range(node_count+1)] + for _ in range(edge_count): + print("Enter the edge's Nodes in form of `source target`\n") + source,target = map(int,input().split(' ')) + adjacency[source].append(target) + adjacency[target].append(source) + print("Total number of Connected Components are : ", count_components(adjacency,node_count)) - # Driver code if __name__ == '__main__': - n,m = map(int, input("Enter the Number of Nodes and Edges \n").split(' ')) - l = [[] for _ in range(n+1)] - for i in range(m): - print("Enter the edge's Nodes in form of a b\n") - a,b = map(int,input().split(' ')) - l[a].append(b) - l[b].append(a) - print("Total number of Connected Components are : ", count_components(l,n)) + main() diff --git a/algorithms/graph/cycle_detection.py b/algorithms/graph/cycle_detection.py index 61cf61c1f..0821b1c0a 100644 --- a/algorithms/graph/cycle_detection.py +++ b/algorithms/graph/cycle_detection.py @@ -9,27 +9,22 @@ class TraversalState(Enum): + """ + For a given node: + - WHITE: has not been visited yet + - GRAY: is currently being investigated for a cycle + - BLACK: is not part of a cycle + """ WHITE = 0 GRAY = 1 BLACK = 2 - -example_graph_with_cycle = {'A': ['B', 'C'], - 'B': ['D'], - 'C': ['F'], - 'D': ['E', 'F'], - 'E': ['B'], - 'F': []} - -example_graph_without_cycle = {'A': ['B', 'C'], - 'B': ['D', 'E'], - 'C': ['F'], - 'D': ['E'], - 'E': [], - 'F': []} - - def is_in_cycle(graph, traversal_states, vertex): + """ + Determines if the given vertex is in a cycle. + + :param: traversal_states: for each vertex, the state it is in + """ if traversal_states[vertex] == TraversalState.GRAY: return True traversal_states[vertex] = TraversalState.GRAY @@ -41,12 +36,20 @@ def is_in_cycle(graph, traversal_states, vertex): def contains_cycle(graph): + """ + Determines if there is a cycle in the given graph. + The graph should be given as a dictionary: + + graph = {'A': ['B', 'C'], + 'B': ['D'], + 'C': ['F'], + 'D': ['E', 'F'], + 'E': ['B'], + 'F': []} + """ traversal_states = {vertex: TraversalState.WHITE for vertex in graph} for vertex, state in traversal_states.items(): if (state == TraversalState.WHITE and is_in_cycle(graph, traversal_states, vertex)): return True return False - -print(contains_cycle(example_graph_with_cycle)) -print(contains_cycle(example_graph_without_cycle)) diff --git a/algorithms/graph/dijkstra.py b/algorithms/graph/dijkstra.py index 2cf043072..e5022e3af 100644 --- a/algorithms/graph/dijkstra.py +++ b/algorithms/graph/dijkstra.py @@ -1,36 +1,49 @@ -#Dijkstra's single source shortest path algorithm +""" +Dijkstra's single-source shortest-path algorithm +""" class Dijkstra(): + """ + A fully connected directed graph with edge weights + """ - def __init__(self, vertices): - self.vertices = vertices - self.graph = [[0 for column in range(vertices)] for row in range(vertices)] + def __init__(self, vertex_count): + self.vertex_count = vertex_count + self.graph = [[0 for _ in range(vertex_count)] for _ in range(vertex_count)] def min_distance(self, dist, min_dist_set): + """ + Find the vertex that is closest to the visited set + """ min_dist = float("inf") - for v in range(self.vertices): - if dist[v] < min_dist and min_dist_set[v] == False: - min_dist = dist[v] - min_index = v + for target in range(self.vertex_count): + if min_dist_set[target]: + continue + if dist[target] < min_dist: + min_dist = dist[target] + min_index = target return min_index def dijkstra(self, src): - - dist = [float("inf")] * self.vertices + """ + Given a node, returns the shortest distance to every other node + """ + dist = [float("inf")] * self.vertex_count dist[src] = 0 - min_dist_set = [False] * self.vertices - - for count in range(self.vertices): + min_dist_set = [False] * self.vertex_count + for _ in range(self.vertex_count): #minimum distance vertex that is not processed - u = self.min_distance(dist, min_dist_set) + source = self.min_distance(dist, min_dist_set) #put minimum distance vertex in shortest tree - min_dist_set[u] = True + min_dist_set[source] = True #Update dist value of the adjacent vertices - for v in range(self.vertices): - if self.graph[u][v] > 0 and min_dist_set[v] == False and dist[v] > dist[u] + self.graph[u][v]: - dist[v] = dist[u] + self.graph[u][v] + for target in range(self.vertex_count): + if self.graph[source][target] <= 0 or min_dist_set[target]: + continue + if dist[target] > dist[source] + self.graph[source][target]: + dist[target] = dist[source] + self.graph[source][target] return dist diff --git a/algorithms/graph/find_all_cliques.py b/algorithms/graph/find_all_cliques.py index 10f7a24eb..f1db16ed5 100644 --- a/algorithms/graph/find_all_cliques.py +++ b/algorithms/graph/find_all_cliques.py @@ -1,12 +1,19 @@ -# takes dict of sets -# each key is a vertex -# value is set of all edges connected to vertex -# returns list of lists (each sub list is a maximal clique) -# implementation of the basic algorithm described in: -# Bron, Coen; Kerbosch, Joep (1973), "Algorithm 457: finding all cliques of an undirected graph", - +""" +Finds all cliques in an undirected graph. A clique is a set of vertices in the +graph such that the subgraph is fully connected (ie. for any pair of nodes in +the subgraph there is an edge between them). +""" def find_all_cliques(edges): + """ + takes dict of sets + each key is a vertex + value is set of all edges connected to vertex + returns list of lists (each sub list is a maximal clique) + implementation of the basic algorithm described in: + Bron, Coen; Kerbosch, Joep (1973), "Algorithm 457: finding all cliques of an undirected graph", + """ + def expand_clique(candidates, nays): nonlocal compsub if not candidates and not nays: diff --git a/algorithms/graph/find_path.py b/algorithms/graph/find_path.py index a18800555..253e43ca3 100644 --- a/algorithms/graph/find_path.py +++ b/algorithms/graph/find_path.py @@ -1,14 +1,14 @@ -myGraph = {'A': ['B', 'C'], - 'B': ['C', 'D'], - 'C': ['D', 'F'], - 'D': ['C'], - 'E': ['F'], - 'F': ['C']} +""" +Functions for finding paths in graphs. +""" -# find path from start to end using recursion with backtracking +# pylint: disable=dangerous-default-value def find_path(graph, start, end, path=[]): + """ + Find a path between two nodes using recursion and backtracking. + """ path = path + [start] - if (start == end): + if start == end: return path if not start in graph: return None @@ -18,13 +18,16 @@ def find_path(graph, start, end, path=[]): return newpath return None -# find all path +# pylint: disable=dangerous-default-value def find_all_path(graph, start, end, path=[]): + """ + Find all paths between two nodes using recursion and backtracking + """ path = path + [start] - if (start == end): + if start == end: return [path] if not start in graph: - return None + return [] paths = [] for node in graph[start]: if node not in path: @@ -34,6 +37,9 @@ def find_all_path(graph, start, end, path=[]): return paths def find_shortest_path(graph, start, end, path=[]): + """ + find the shortest path between two nodes + """ path = path + [start] if start == end: return path @@ -47,6 +53,3 @@ def find_shortest_path(graph, start, end, path=[]): if not shortest or len(newpath) < len(shortest): shortest = newpath return shortest - -print(find_all_path(myGraph, 'A', 'F')) -# print(find_shortest_path(myGraph, 'A', 'D')) diff --git a/algorithms/graph/graph.py b/algorithms/graph/graph.py index 31e564599..5d4305933 100644 --- a/algorithms/graph/graph.py +++ b/algorithms/graph/graph.py @@ -3,24 +3,31 @@ It can be shared across graph algorithms. """ -class Node(object): +class Node: + """ + A node/vertex in a graph. + """ + def __init__(self, name): self.name = name @staticmethod def get_name(obj): + """ + Return the name of the node + """ if isinstance(obj, Node): return obj.name - elif isinstance(obj, str): + if isinstance(obj, str): return obj return'' - + def __eq__(self, obj): return self.name == self.get_name(obj) def __repr__(self): return self.name - + def __hash__(self): return hash(self.name) @@ -42,72 +49,63 @@ def __ge__(self, obj): def __bool__(self): return self.name -class DirectedEdge(object): +class DirectedEdge: + """ + A directed edge in a directed graph. + Stores the source and target node of the edge. + """ + def __init__(self, node_from, node_to): - self.nf = node_from - self.nt = node_to + self.source = node_from + self.target = node_to def __eq__(self, obj): if isinstance(obj, DirectedEdge): - return obj.nf == self.nf and obj.nt == self.nt + return obj.source == self.source and obj.target == self.target return False - + def __repr__(self): - return '({0} -> {1})'.format(self.nf, self.nt) + return f"({self.source} -> {self.target})" + +class DirectedGraph: + """ + A directed graph. + Stores a set of nodes, edges and adjacency matrix. + """ -class DirectedGraph(object): + # pylint: disable=dangerous-default-value def __init__(self, load_dict={}): self.nodes = [] self.edges = [] - self.adjmt = {} + self.adjacency_list = {} - if load_dict and type(load_dict) == dict: - for v in load_dict: - node_from = self.add_node(v) - self.adjmt[node_from] = [] - for w in load_dict[v]: - node_to = self.add_node(w) - self.adjmt[node_from].append(node_to) - self.add_edge(v, w) + if load_dict and isinstance(load_dict, dict): + for vertex in load_dict: + node_from = self.add_node(vertex) + self.adjacency_list[node_from] = [] + for neighbor in load_dict[vertex]: + node_to = self.add_node(neighbor) + self.adjacency_list[node_from].append(node_to) + self.add_edge(vertex, neighbor) def add_node(self, node_name): + """ + Add a new named node to the graph. + """ try: return self.nodes[self.nodes.index(node_name)] except ValueError: node = Node(node_name) self.nodes.append(node) return node - + def add_edge(self, node_name_from, node_name_to): + """ + Add a new edge to the graph between two nodes. + """ try: node_from = self.nodes[self.nodes.index(node_name_from)] node_to = self.nodes[self.nodes.index(node_name_to)] self.edges.append(DirectedEdge(node_from, node_to)) except ValueError: pass - -class Graph: - def __init__(self, vertices): - # No. of vertices - self.V = vertices - - # default dictionary to store graph - self.graph = {} - - # To store transitive closure - self.tc = [[0 for j in range(self.V)] for i in range(self.V)] - - # function to add an edge to graph - def add_edge(self, u, v): - if u in self.graph: - self.graph[u].append(v) - else: - self.graph[u] = [v] - -#g = Graph(4) -#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) diff --git a/algorithms/graph/markov_chain.py b/algorithms/graph/markov_chain.py index c588311d2..78c50bd12 100644 --- a/algorithms/graph/markov_chain.py +++ b/algorithms/graph/markov_chain.py @@ -1,26 +1,39 @@ -import random +""" +Implements a markov chain. Chains are described using a dictionary: + + my_chain = { + 'A': {'A': 0.6, + 'E': 0.4}, + 'E': {'A': 0.7, + 'E': 0.3} + } +""" -my_chain = { - 'A': {'A': 0.6, - 'E': 0.4}, - 'E': {'A': 0.7, - 'E': 0.3} -} +import random def __choose_state(state_map): + """ + Choose the next state randomly + """ choice = random.random() probability_reached = 0 for state, probability in state_map.items(): probability_reached += probability if probability_reached > choice: return state + return None def next_state(chain, current_state): + """ + Given a markov-chain, randomly chooses the next state given the current state. + """ next_state_map = chain.get(current_state) - next_state = __choose_state(next_state_map) - return next_state + return __choose_state(next_state_map) def iterating_markov_chain(chain, state): + """ + Yield a sequence of states given a markov chain and the initial state + """ while True: state = next_state(chain, state) yield state diff --git a/algorithms/graph/maximum_flow.py b/algorithms/graph/maximum_flow.py index ca7824482..b46e70790 100644 --- a/algorithms/graph/maximum_flow.py +++ b/algorithms/graph/maximum_flow.py @@ -8,16 +8,22 @@ If there is no edge from i to j, capacity[i][j] should be zero. """ -import queue +from queue import Queue +# pylint: disable=too-many-arguments def dfs(capacity, flow, visit, vertices, idx, sink, current_flow = 1 << 63): + """ + Depth First Search implementation for Ford-Fulkerson algorithm. + """ + # DFS function for ford_fulkerson algorithm. - if idx == sink: + if idx == sink: return current_flow visit[idx] = True for nxt in range(vertices): if not visit[nxt] and flow[idx][nxt] < capacity[idx][nxt]: - tmp = dfs(capacity, flow, visit, vertices, nxt, sink, min(current_flow, capacity[idx][nxt]-flow[idx][nxt])) + available_flow = min(current_flow, capacity[idx][nxt]-flow[idx][nxt]) + tmp = dfs(capacity, flow, visit, vertices, nxt, sink, available_flow) if tmp: flow[idx][nxt] += tmp flow[nxt][idx] -= tmp @@ -25,38 +31,42 @@ def dfs(capacity, flow, visit, vertices, idx, sink, current_flow = 1 << 63): return 0 def ford_fulkerson(capacity, source, sink): - # Computes maximum flow from source to sink using DFS. - # Time Complexity : O(Ef) - # E is the number of edges and f is the maximum flow in the graph. + """ + Computes maximum flow from source to sink using DFS. + Time Complexity : O(Ef) + E is the number of edges and f is the maximum flow in the graph. + """ vertices = len(capacity) ret = 0 - flow = [[0]*vertices for i in range(vertices)] + flow = [[0]*vertices for _ in range(vertices)] while True: - visit = [False for i in range(vertices)] + visit = [False for _ in range(vertices)] tmp = dfs(capacity, flow, visit, vertices, source, sink) - if tmp: + if tmp: ret += tmp - else: + else: break return ret def edmonds_karp(capacity, source, sink): - # Computes maximum flow from source to sink using BFS. - # Time complexity : O(V*E^2) - # V is the number of vertices and E is the number of edges. + """ + Computes maximum flow from source to sink using BFS. + Time complexity : O(V*E^2) + V is the number of vertices and E is the number of edges. + """ vertices = len(capacity) ret = 0 - flow = [[0]*vertices for i in range(vertices)] + flow = [[0]*vertices for _ in range(vertices)] while True: tmp = 0 - q = queue.Queue() - visit = [False for i in range(vertices)] - par = [-1 for i in range(vertices)] + queue = Queue() + visit = [False for _ in range(vertices)] + par = [-1 for _ in range(vertices)] visit[source] = True - q.put((source, 1 << 63)) + queue.put((source, 1 << 63)) # Finds new flow using BFS. - while q.qsize(): - front = q.get() + while queue.qsize(): + front = queue.get() idx, current_flow = front if idx == sink: tmp = current_flow @@ -65,8 +75,8 @@ def edmonds_karp(capacity, source, sink): if not visit[nxt] and flow[idx][nxt] < capacity[idx][nxt]: visit[nxt] = True par[nxt] = idx - q.put((nxt, min(current_flow, capacity[idx][nxt]-flow[idx][nxt]))) - if par[sink] == -1: + queue.put((nxt, min(current_flow, capacity[idx][nxt]-flow[idx][nxt]))) + if par[sink] == -1: break ret += tmp parent = par[sink] @@ -80,31 +90,35 @@ def edmonds_karp(capacity, source, sink): return ret def dinic_bfs(capacity, flow, level, source, sink): - # BFS function for Dinic algorithm. - # Check whether sink is reachable only using edges that is not full. - + """ + BFS function for Dinic algorithm. + Check whether sink is reachable only using edges that is not full. + """ vertices = len(capacity) - q = queue.Queue() - q.put(source) + queue = Queue() + queue.put(source) level[source] = 0 - while q.qsize(): - front = q.get() + while queue.qsize(): + front = queue.get() for nxt in range(vertices): if level[nxt] == -1 and flow[front][nxt] < capacity[front][nxt]: level[nxt] = level[front] + 1 - q.put(nxt) + queue.put(nxt) return level[sink] != -1 def dinic_dfs(capacity, flow, level, idx, sink, work, current_flow = 1 << 63): - # DFS function for Dinic algorithm. - # Finds new flow using edges that is not full. + """ + DFS function for Dinic algorithm. + Finds new flow using edges that is not full. + """ if idx == sink: return current_flow vertices = len(capacity) while work[idx] < vertices: nxt = work[idx] if level[nxt] == level[idx] + 1 and flow[idx][nxt] < capacity[idx][nxt]: - tmp = dinic_dfs(capacity, flow, level, nxt, sink, work, min(current_flow, capacity[idx][nxt] - flow[idx][nxt])) + available_flow = min(current_flow, capacity[idx][nxt] - flow[idx][nxt]) + tmp = dinic_dfs(capacity, flow, level, nxt, sink, work, available_flow) if tmp > 0: flow[idx][nxt] += tmp flow[nxt][idx] -= tmp @@ -113,9 +127,11 @@ def dinic_dfs(capacity, flow, level, idx, sink, work, current_flow = 1 << 63): return 0 def dinic(capacity, source, sink): - # Computes maximum flow from source to sink using Dinic algorithm. - # Time complexity : O(V^2*E) - # V is the number of vertices and E is the number of edges. + """ + Computes maximum flow from source to sink using Dinic algorithm. + Time complexity : O(V^2*E) + V is the number of vertices and E is the number of edges. + """ vertices = len(capacity) flow = [[0]*vertices for i in range(vertices)] ret = 0 @@ -131,4 +147,3 @@ def dinic(capacity, source, sink): else: break return ret - diff --git a/algorithms/graph/maximum_flow_bfs.py b/algorithms/graph/maximum_flow_bfs.py index 4d2d032ff..bd056c8d3 100644 --- a/algorithms/graph/maximum_flow_bfs.py +++ b/algorithms/graph/maximum_flow_bfs.py @@ -9,12 +9,12 @@ example -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]] answer should be @@ -26,27 +26,30 @@ import math def maximum_flow_bfs(adjacency_matrix): + """ + Get the maximum flow through a graph using a breadth first search + """ #initial setting new_array = copy.deepcopy(adjacency_matrix) total = 0 - while(1): + while True: #setting min to max_value - min = math.inf + min_flow = math.inf #save visited nodes visited = [0]*len(new_array) #save parent nodes path = [0]*len(new_array) - + #initialize queue for BFS bfs = queue.Queue() - #initial setting + #initial setting visited[0] = 1 bfs.put(0) #BFS to find path - while(bfs.qsize() > 0): + while bfs.qsize() > 0: #pop from queue src = bfs.get() for k in range(len(new_array)): @@ -56,29 +59,29 @@ def maximum_flow_bfs(adjacency_matrix): visited[k] = 1 bfs.put(k) path[k] = src - + #if there is no path from src to sink - if(visited[len(new_array) - 1] == 0): + if visited[len(new_array) - 1] == 0: break - + #initial setting tmp = len(new_array) - 1 #Get minimum flow - while(tmp != 0): + while tmp != 0: #find minimum flow - if(min > new_array[path[tmp]][tmp]): - min = new_array[path[tmp]][tmp] + if min_flow > new_array[path[tmp]][tmp]: + min_flow = new_array[path[tmp]][tmp] tmp = path[tmp] #initial setting tmp = len(new_array) - 1 #reduce capacity - while(tmp != 0): - new_array[path[tmp]][tmp] = new_array[path[tmp]][tmp] - min + while tmp != 0: + new_array[path[tmp]][tmp] = new_array[path[tmp]][tmp] - min_flow tmp = path[tmp] - total = total + min - + total = total + min_flow + return total diff --git a/algorithms/graph/maximum_flow_dfs.py b/algorithms/graph/maximum_flow_dfs.py index 2d89d6b56..e27c4e851 100644 --- a/algorithms/graph/maximum_flow_dfs.py +++ b/algorithms/graph/maximum_flow_dfs.py @@ -9,12 +9,12 @@ example -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]] answer should be @@ -25,48 +25,52 @@ import math def maximum_flow_dfs(adjacency_matrix): + """ + Get the maximum flow through a graph using a depth first search + """ + #initial setting new_array = copy.deepcopy(adjacency_matrix) total = 0 - while(1): + while True: #setting min to max_value min = math.inf #save visited nodes visited = [0]*len(new_array) #save parent nodes path = [0]*len(new_array) - + #initialize stack for DFS stack = [] - #initial setting + #initial setting visited[0] = 1 stack.append(0) #DFS to find path - while(len(stack) > 0): + while len(stack) > 0: #pop from queue src = stack.pop() for k in range(len(new_array)): #checking capacity and visit - if(new_array[src][k] > 0 and visited[k] == 0 ): + if new_array[src][k] > 0 and visited[k] == 0: #if not, put into queue and chage to visit and save path visited[k] = 1 stack.append(k) path[k] = src - + #if there is no path from src to sink - if(visited[len(new_array) - 1] == 0): + if visited[len(new_array) - 1] == 0: break - + #initial setting tmp = len(new_array) - 1 #Get minimum flow - while(tmp != 0): + while tmp != 0: #find minimum flow - if(min > new_array[path[tmp]][tmp]): + if min > new_array[path[tmp]][tmp]: min = new_array[path[tmp]][tmp] tmp = path[tmp] @@ -74,11 +78,11 @@ def maximum_flow_dfs(adjacency_matrix): tmp = len(new_array) - 1 #reduce capacity - while(tmp != 0): + while tmp != 0: new_array[path[tmp]][tmp] = new_array[path[tmp]][tmp] - min tmp = path[tmp] total = total + min - + return total - \ No newline at end of file + diff --git a/algorithms/graph/minimum_spanning_tree.py b/algorithms/graph/minimum_spanning_tree.py index dcc6898b6..d53a957ca 100644 --- a/algorithms/graph/minimum_spanning_tree.py +++ b/algorithms/graph/minimum_spanning_tree.py @@ -1,130 +1,151 @@ -# Minimum spanning tree (MST) is going to use an undirected graph -# -# The disjoint set is represented with an list of integers where -# is the parent of the node at position . -# If = , it's a root, or a head, of a set +""" +Minimum spanning tree (MST) is going to use an undirected graph +""" +import sys +# pylint: disable=too-few-public-methods class Edge: - def __init__(self, u, v, weight): - self.u = u - self.v = v + """ + An edge of an undirected graph + """ + + def __init__(self, source, target, weight): + self.source = source + self.target = target self.weight = weight class DisjointSet: - def __init__(self, n): - # Args: - # n (int): Number of vertices in the graph - - self.parent = [None] * n # Contains wich node is the parent of the node at poisition - self.size = [1] * n # Contains size of node at index , used to optimize merge - for i in range(n): + """ + The disjoint set is represented with an list of integers where + is the parent of the node at position . + If = , it's a root, or a head, of a set + """ + + def __init__(self, size): + """ + Args: + n (int): Number of vertices in the graph + """ + + self.parent = [None] * size # Contains wich node is the parent of the node at poisition + self.size = [1] * size # Contains size of node at index , used to optimize merge + for i in range(size): self.parent[i] = i # Make all nodes his own parent, creating n sets. - def merge_set(self, a, b): - # Args: - # a, b (int): Indexes of nodes whose sets will be merged. + def merge_set(self, node1, node2): + """ + Args: + node1, node2 (int): Indexes of nodes whose sets will be merged. + """ # Get the set of nodes at position and # If and are the roots, this will be constant O(1) - a = self.find_set(a) - b = self.find_set(b) + node1 = self.find_set(node1) + node2 = self.find_set(node2) # Join the shortest node to the longest, minimizing tree size (faster find) - if self.size[a] < self.size[b]: - self.parent[a] = b # Merge set(a) and set(b) - self.size[b] += self.size[a] # Add size of old set(a) to set(b) + if self.size[node1] < self.size[node2]: + self.parent[node1] = node2 # Merge set(a) and set(b) + self.size[node2] += self.size[node1] # Add size of old set(a) to set(b) else: - self.parent[b] = a # Merge set(b) and set(a) - self.size[a] += self.size[b] # Add size of old set(b) to set(a) - - def find_set(self, a): - if self.parent[a] != a: - # Very important, memoize result of the + self.parent[node2] = node1 # Merge set(b) and set(a) + self.size[node1] += self.size[node2] # Add size of old set(b) to set(a) + + def find_set(self, node): + """ + Get the root element of the set containing + """ + if self.parent[node] != node: + # Very important, memoize result of the # recursion in the list to optimize next # calls and make this operation practically constant, O(1) - self.parent[a] = self.find_set(self.parent[a]) + self.parent[node] = self.find_set(self.parent[node]) # node it's the set root, so we can return that index - return self.parent[a] - - -def kruskal(n, edges, ds): - # Args: - # n (int): Number of vertices in the graph - # edges (list of Edge): Edges of the graph - # ds (DisjointSet): DisjointSet of the vertices - # Returns: - # int: sum of weights of the minnimum spanning tree - # - # Kruskal algorithm: - # This algorithm will find the optimal graph with less edges and less - # total weight to connect all vertices (MST), the MST will always contain - # n-1 edges because it's the minimum required to connect n vertices. - # - # Procedure: - # Sort the edges (criteria: less weight). - # Only take edges of nodes in different sets. - # If we take a edge, we need to merge the sets to discard these. - # After repeat this until select n-1 edges, we will have the complete MST. + return self.parent[node] + + +def kruskal(vertex_count, edges, forest): + """ + Args: + vertex_count (int): Number of vertices in the graph + edges (list of Edge): Edges of the graph + forest (DisjointSet): DisjointSet of the vertices + Returns: + int: sum of weights of the minnimum spanning tree + + Kruskal algorithm: + This algorithm will find the optimal graph with less edges and less + total weight to connect all vertices (MST), the MST will always contain + n-1 edges because it's the minimum required to connect n vertices. + + Procedure: + Sort the edges (criteria: less weight). + Only take edges of nodes in different sets. + If we take a edge, we need to merge the sets to discard these. + After repeat this until select n-1 edges, we will have the complete MST. + """ edges.sort(key=lambda edge: edge.weight) mst = [] # List of edges taken, minimum spanning tree for edge in edges: - set_u = ds.find_set(edge.u) # Set of the node - set_v = ds.find_set(edge.v) # Set of the node + set_u = forest.find_set(edge.u) # Set of the node + set_v = forest.find_set(edge.v) # Set of the node if set_u != set_v: - ds.merge_set(set_u, set_v) + forest.merge_set(set_u, set_v) mst.append(edge) - if len(mst) == n-1: - # If we have selected n-1 edges, all the other + if len(mst) == vertex_count-1: + # If we have selected n-1 edges, all the other # edges will be discarted, so, we can stop here break return sum([edge.weight for edge in mst]) - - -if __name__ == "__main__": - # Test. How input works: - # Input consists of different weighted, connected, undirected graphs. - # line 1: - # integers n, m - # lines 2..m+2: - # edge with the format -> node index u, node index v, integer weight - # - # Samples of input: - # - # 5 6 - # 1 2 3 - # 1 3 8 - # 2 4 5 - # 3 4 2 - # 3 5 4 - # 4 5 6 - # - # 3 3 - # 2 1 20 - # 3 1 20 - # 2 3 100 - # - # Sum of weights of the optimal paths: - # 14, 40 - import sys - for n_m in sys.stdin: - n, m = map(int, n_m.split()) - ds = DisjointSet(m) - edges = [None] * m # Create list of size +def main(): + """ + Test. How input works: + Input consists of different weighted, connected, undirected graphs. + line 1: + integers n, m + lines 2..m+2: + edge with the format -> node index u, node index v, integer weight + + Samples of input: + + 5 6 + 1 2 3 + 1 3 8 + 2 4 5 + 3 4 2 + 3 5 4 + 4 5 6 + + 3 3 + 2 1 20 + 3 1 20 + 2 3 100 + + Sum of weights of the optimal paths: + 14, 40 + """ + for size in sys.stdin: + vertex_count, edge_count = map(int, size.split()) + forest = DisjointSet(edge_count) + edges = [None] * edge_count # Create list of size # Read edges from input - for i in range(m): - u, v, weight = map(int, input().split()) - u -= 1 # Convert from 1-indexed to 0-indexed - v -= 1 # Convert from 1-indexed to 0-indexed - edges[i] = Edge(u, v, weight) + for i in range(edge_count): + source, target, weight = map(int, input().split()) + source -= 1 # Convert from 1-indexed to 0-indexed + target -= 1 # Convert from 1-indexed to 0-indexed + edges[i] = Edge(source, target, weight) # After finish input and graph creation, use Kruskal algorithm for MST: - print("MST weights sum:", kruskal(n, edges, ds)) + print("MST weights sum:", kruskal(vertex_count, edges, forest)) + +if __name__ == "__main__": + main() diff --git a/algorithms/graph/path_between_two_vertices_in_digraph.py b/algorithms/graph/path_between_two_vertices_in_digraph.py index f85513320..4bf290253 100644 --- a/algorithms/graph/path_between_two_vertices_in_digraph.py +++ b/algorithms/graph/path_between_two_vertices_in_digraph.py @@ -1,52 +1,51 @@ +""" +Determine if there is a path between nodes in a graph +""" + from collections import defaultdict class Graph: - def __init__(self, v): - self.v = v - self.graph = defaultdict(list) - self.has_path = False - - def add_edge(self, u, v): - self.graph[u].append(v) - - def dfs(self, x, y): - visited = [False] * self.v - self.dfsutil(visited, x, y,) - - def dfsutil(self, visited, x, y): - visited[x] = True - for i in self.graph[x]: - if y in self.graph[x]: - self.has_path = True - return - if(not(visited[i])): - self.dfsutil(visited, x, i) - - def is_reachable(self, x, y): - self.has_path = False - self.dfs(x, y) - return self.has_path - - -# Create a graph given in the above diagram -g = Graph(4) -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) - -u, v = 1, 3 - -if g.is_reachable(u, v): - print("There is a path from %d to %d" % (u, v)) -else: - print("There is no path from %d to %d" % (u, v)) - -u, v = 1, 3 -if g.is_reachable(u, v): - print("There is a path from %d to %d" % (u, v)) -else: - print("There is no path from %d to %d" % (u, v)) + """ + A directed graph + """ + + def __init__(self,vertex_count): + self.vertex_count = vertex_count + self.graph = defaultdict(list) + self.has_path = False + + def add_edge(self,source,target): + """ + Add a new directed edge to the graph + """ + self.graph[source].append(target) + + def dfs(self,source,target): + """ + Determine if there is a path from source to target using a depth first search + """ + visited = [False] * self.vertex_count + self.dfsutil(visited,source,target,) + + def dfsutil(self,visited,source,target): + """ + Determine if there is a path from source to target using a depth first search. + :param: visited should be an array of booleans determining if the + corresponding vertex has been visited already + """ + visited[source] = True + for i in self.graph[source]: + if target in self.graph[source]: + self.has_path = True + return + if not visited[i]: + self.dfsutil(visited,source,i) + + def is_reachable(self,source,target): + """ + Determine if there is a path from source to target + """ + self.has_path = False + self.dfs(source,target) + return self.has_path diff --git a/algorithms/graph/prims_minimum_spanning.py b/algorithms/graph/prims_minimum_spanning.py index 02295c4c2..af7cb4357 100644 --- a/algorithms/graph/prims_minimum_spanning.py +++ b/algorithms/graph/prims_minimum_spanning.py @@ -1,15 +1,15 @@ ''' This Prim's Algorithm Code is for finding weight of minimum spanning tree of a connected graph. -For argument graph, it should be a dictionary type -such as -graph = { - 'a': [ [3, 'b'], [8,'c'] ], - 'b': [ [3, 'a'], [5, 'd'] ], - 'c': [ [8, 'a'], [2, 'd'], [4, 'e'] ], - 'd': [ [5, 'b'], [2, 'c'], [6, 'e'] ], - 'e': [ [4, 'c'], [6, 'd'] ] -} +For argument graph, it should be a dictionary type such as: + + graph = { + 'a': [ [3, 'b'], [8,'c'] ], + 'b': [ [3, 'a'], [5, 'd'] ], + 'c': [ [8, 'a'], [2, 'd'], [4, 'e'] ], + 'd': [ [5, 'b'], [2, 'c'], [6, 'e'] ], + 'e': [ [4, 'c'], [6, 'd'] ] + } where 'a','b','c','d','e' are nodes (these can be 1,2,3,4,5 as well) ''' @@ -17,26 +17,26 @@ import heapq # for priority queue -# prim's algo. to find weight of minimum spanning tree def prims_minimum_spanning(graph_used): + """ + Prim's algorithm to find weight of minimum spanning tree + """ vis=[] - s=[[0,1]] - prim = [] + heap=[[0,1]] + prim = set() mincost=0 - - while(len(s)>0): - v=heapq.heappop(s) - x=v[1] - if(x in vis): + + while len(heap) > 0: + cost, node = heapq.heappop(heap) + if node in vis: continue - mincost += v[0] - prim.append(x) - vis.append(x) + mincost += cost + prim.add(node) + vis.append(node) - for j in graph_used[x]: - i=j[-1] - if(i not in vis): - heapq.heappush(s,j) + for distance, adjacent in graph_used[node]: + if adjacent not in vis: + heapq.heappush(heap, [distance, adjacent]) return mincost diff --git a/algorithms/graph/satisfiability.py b/algorithms/graph/satisfiability.py index bc1d1892e..0cae8ee92 100644 --- a/algorithms/graph/satisfiability.py +++ b/algorithms/graph/satisfiability.py @@ -1,45 +1,49 @@ -''' +""" Given a formula in conjunctive normal form (2-CNF), finds a way to assign True/False values to all variables to satisfy all clauses, or reports there is no solution. https://en.wikipedia.org/wiki/2-satisfiability -''' -''' Format: +Format: - each clause is a pair of literals - each literal in the form (name, is_neg) where name is an arbitrary identifier, and is_neg is true if the literal is negated -''' -formula = [(('x', False), ('y', False)), - (('y', True), ('y', True)), - (('a', False), ('b', False)), - (('a', True), ('c', True)), - (('c', False), ('b', True))] +""" +def dfs_transposed(vertex, graph, order, visited): + """ + Perform a depth first search traversal of the graph starting at the given vertex. + Stores the order in which nodes were visited to the list, in transposed order. + """ + visited[vertex] = True -def dfs_transposed(v, graph, order, vis): - vis[v] = True + for adjacent in graph[vertex]: + if not visited[adjacent]: + dfs_transposed(adjacent, graph, order, visited) - for u in graph[v]: - if not vis[u]: - dfs_transposed(u, graph, order, vis) + order.append(vertex) - order.append(v) +def dfs(vertex, current_comp, vertex_scc, graph, visited): + """ + Perform a depth first search traversal of the graph starting at the given vertex. + Records all visited nodes as being of a certain strongly connected component. + """ + visited[vertex] = True + vertex_scc[vertex] = current_comp -def dfs(v, current_comp, vertex_scc, graph, vis): - vis[v] = True - vertex_scc[v] = current_comp - - for u in graph[v]: - if not vis[u]: - dfs(u, current_comp, vertex_scc, graph, vis) + for adjacent in graph[vertex]: + if not visited[adjacent]: + dfs(adjacent, current_comp, vertex_scc, graph, visited) def add_edge(graph, vertex_from, vertex_to): + """ + Add a directed edge to the graph. + """ if vertex_from not in graph: graph[vertex_from] = [] @@ -49,26 +53,26 @@ def add_edge(graph, vertex_from, vertex_to): def scc(graph): ''' Computes the strongly connected components of a graph ''' order = [] - vis = {vertex: False for vertex in graph} + visited = {vertex: False for vertex in graph} graph_transposed = {vertex: [] for vertex in graph} - for (v, neighbours) in graph.iteritems(): - for u in neighbours: - add_edge(graph_transposed, u, v) + for (source, neighbours) in graph.iteritems(): + for target in neighbours: + add_edge(graph_transposed, target, source) - for v in graph: - if not vis[v]: - dfs_transposed(v, graph_transposed, order, vis) + for vertex in graph: + if not visited[vertex]: + dfs_transposed(vertex, graph_transposed, order, visited) - vis = {vertex: False for vertex in graph} + visited = {vertex: False for vertex in graph} vertex_scc = {} current_comp = 0 - for v in reversed(order): - if not vis[v]: + for vertex in reversed(order): + if not visited[vertex]: # Each dfs will visit exactly one component - dfs(v, current_comp, vertex_scc, graph, vis) + dfs(vertex, current_comp, vertex_scc, graph, visited) current_comp += 1 return vertex_scc @@ -91,6 +95,9 @@ def build_graph(formula): def solve_sat(formula): + """ + Solves the 2-SAT problem + """ graph = build_graph(formula) vertex_scc = scc(graph) @@ -119,8 +126,20 @@ def solve_sat(formula): return value -if __name__ == '__main__': +def main(): + """ + Entry point for testing + """ + formula = [(('x', False), ('y', False)), + (('y', True), ('y', True)), + (('a', False), ('b', False)), + (('a', True), ('c', True)), + (('c', False), ('b', True))] + result = solve_sat(formula) - for (variable, assign) in result.iteritems(): - print("{}:{}".format(variable, assign)) + for (variable, assign) in result.items(): + print(f"{variable}:{assign}") + +if __name__ == '__main__': + main() diff --git a/algorithms/graph/tarjan.py b/algorithms/graph/tarjan.py index 399a39d6d..ebc69326c 100644 --- a/algorithms/graph/tarjan.py +++ b/algorithms/graph/tarjan.py @@ -6,7 +6,11 @@ from algorithms.graph.graph import DirectedGraph -class Tarjan(object): +# pylint: disable=too-few-public-methods +class Tarjan: + """ + A directed graph used for finding strongly connected components + """ def __init__(self, dict_graph): self.graph = DirectedGraph(dict_graph) self.index = 0 @@ -14,44 +18,48 @@ def __init__(self, dict_graph): # Runs Tarjan # Set all node index to None - for v in self.graph.nodes: - v.index = None + for vertex in self.graph.nodes: + vertex.index = None self.sccs = [] - for v in self.graph.nodes: - if v.index is None: - self.strongconnect(v, self.sccs) + for vertex in self.graph.nodes: + if vertex.index is None: + self.strongconnect(vertex, self.sccs) - def strongconnect(self, v, sccs): + def strongconnect(self, vertex, sccs): + """ + Given a vertex, adds all successors of the given vertex to the same connected component + """ # Set the depth index for v to the smallest unused index - v.index = self.index - v.lowlink = self.index + vertex.index = self.index + vertex.lowlink = self.index self.index += 1 - self.stack.append(v) - v.on_stack = True + self.stack.append(vertex) + vertex.on_stack = True # Consider successors of v - for w in self.graph.adjmt[v]: - if w.index is None: + for adjacent in self.graph.adjacency_list[vertex]: + if adjacent.index is None: # Successor w has not yet been visited; recurse on it - self.strongconnect(w, sccs) - v.lowlink = min(v.lowlink, w.lowlink) - elif w.on_stack: + self.strongconnect(adjacent, sccs) + vertex.lowlink = min(vertex.lowlink, adjacent.lowlink) + elif adjacent.on_stack: # Successor w is in stack S and hence in the current SCC - # If w is not on stack, then (v, w) is a cross-edge in the DFS tree and must be ignored + # If w is not on stack, then (v, w) is a cross-edge in the DFS + # tree and must be ignored # Note: The next line may look odd - but is correct. # It says w.index not w.lowlink; that is deliberate and from the original paper - v.lowlink = min(v.lowlink, w.index) + vertex.lowlink = min(vertex.lowlink, adjacent.index) # If v is a root node, pop the stack and generate an SCC - if v.lowlink == v.index: + if vertex.lowlink == vertex.index: # start a new strongly connected component scc = [] while True: - w = self.stack.pop() - w.on_stack = False - scc.append(w) - if w == v: + adjacent = self.stack.pop() + adjacent.on_stack = False + scc.append(adjacent) + if adjacent == vertex: break scc.sort() sccs.append(scc) diff --git a/algorithms/graph/transitive_closure_dfs.py b/algorithms/graph/transitive_closure_dfs.py index 655c75c89..ca7e43a1b 100644 --- a/algorithms/graph/transitive_closure_dfs.py +++ b/algorithms/graph/transitive_closure_dfs.py @@ -1,52 +1,55 @@ -# This class represents a directed graph using adjacency +""" +Finds the transitive closure of a graph. + +reference: https://en.wikipedia.org/wiki/Transitive_closure#In_graph_theory +""" + class Graph: + """ + This class represents a directed graph using adjacency lists + """ def __init__(self, vertices): # No. of vertices - self.V = vertices + self.vertex_count = vertices # default dictionary to store graph self.graph = {} # To store transitive closure - self.tc = [[0 for j in range(self.V)] for i in range(self.V)] - - # function to add an edge to graph - def add_edge(self, u, v): - if u in self.graph: - self.graph[u].append(v) + self.closure = [[0 for j in range(vertices)] for i in range(vertices)] + + def add_edge(self, source, target): + """ + Adds a directed edge to the graph + """ + if source in self.graph: + self.graph[source].append(target) else: - self.graph[u] = [v] + self.graph[source] = [target] - # A recursive DFS traversal function that finds - # all reachable vertices for s - def dfs_util(self, s, v): + def dfs_util(self, source, target): + """ + A recursive DFS traversal function that finds + all reachable vertices for source + """ - # Mark reachability from s to v as true. - self.tc[s][v] = 1 + # Mark reachability from source to target as true. + self.closure[source][target] = 1 - # Find all the vertices reachable through v - for i in self.graph[v]: - if self.tc[s][i] == 0: - self.dfs_util(s, i) + # Find all the vertices reachable through target + for adjacent in self.graph[target]: + if self.closure[source][adjacent] == 0: + self.dfs_util(source, adjacent) - # The function to find transitive closure. It uses - # recursive dfs_util() def transitive_closure(self): + """ + The function to find transitive closure. It uses + recursive dfs_util() + """ # Call the recursive helper function to print DFS # traversal starting from all vertices one by one - for i in range(self.V): + for i in range(self.vertex_count): self.dfs_util(i, i) - print(self.tc) - - -# g = Graph(4) -# 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) -# print("Transitive closure matrix is") -# g.transitive_closure() + return self.closure diff --git a/algorithms/graph/traversal.py b/algorithms/graph/traversal.py index cbfc65ff4..19ae14154 100644 --- a/algorithms/graph/traversal.py +++ b/algorithms/graph/traversal.py @@ -1,9 +1,6 @@ -graph = {'A': set(['B', 'C', 'F']), - 'B': set(['A', 'D', 'E']), - 'C': set(['A', 'F']), - 'D': set(['B']), - 'E': set(['B', 'F']), - 'F': set(['A', 'C', 'E'])} +""" +Different ways to traverse a graph +""" # dfs and bfs are the ultimately same except that they are visiting nodes in # different order. To simulate this ordering we would use stack for dfs and @@ -11,56 +8,41 @@ # def dfs_traverse(graph, start): + """ + Traversal by depth first search. + """ visited, stack = set(), [start] while stack: node = stack.pop() if node not in visited: visited.add(node) - for nextNode in graph[node]: - if nextNode not in visited: - stack.append(nextNode) + for next_node in graph[node]: + if next_node not in visited: + stack.append(next_node) return visited -# print(dfs_traverse(graph, 'A')) - - def bfs_traverse(graph, start): + """ + Traversal by breadth first search. + """ visited, queue = set(), [start] while queue: node = queue.pop(0) if node not in visited: visited.add(node) - for nextNode in graph[node]: - if nextNode not in visited: - queue.append(nextNode) + for next_node in graph[node]: + if next_node not in visited: + queue.append(next_node) return visited -# print(bfs_traverse(graph, 'A')) - def dfs_traverse_recursive(graph, start, visited=None): + """ + Traversal by recursive depth first search. + """ if visited is None: visited = set() visited.add(start) - for nextNode in graph[start]: - if nextNode not in visited: - dfs_traverse_recursive(graph, nextNode, visited) + for next_node in graph[start]: + if next_node not in visited: + dfs_traverse_recursive(graph, next_node, visited) return visited - -# print(dfs_traverse_recursive(graph, 'A')) - -# def find_path(graph, start, end, visited=[]): - # # basecase - # visitied = visited + [start] - # if start == end: - # return visited - # if start not in graph: - # return None - # for node in graph[start]: - # if node not in visited: - # new_visited = find_path(graph, node, end, visited) - # return new_visited - # return None - -# print(find_path(graph, 'A', 'F')) - - diff --git a/algorithms/heap/binary_heap.py b/algorithms/heap/binary_heap.py index ac7ff63bc..776e315f6 100644 --- a/algorithms/heap/binary_heap.py +++ b/algorithms/heap/binary_heap.py @@ -1,4 +1,4 @@ -""" +r""" Binary Heap. A min heap is a complete binary tree where each node is smaller than its children. The root, therefore, is the minimum element in the tree. The min heap uses an array to represent the data and operation. For example a min heap: @@ -31,28 +31,39 @@ """ from abc import ABCMeta, abstractmethod + class AbstractHeap(metaclass=ABCMeta): """Abstract Class for Binary Heap.""" + def __init__(self): - pass + """Pass.""" + @abstractmethod def perc_up(self, i): - pass + """Pass.""" + @abstractmethod def insert(self, val): - pass + """Pass.""" + @abstractmethod - def perc_down(self,i): - pass + def perc_down(self, i): + """Pass.""" + @abstractmethod - def min_child(self,i): - pass + def min_child(self, i): + """Pass.""" + @abstractmethod def remove_min(self): - pass + """Pass.""" + + class BinaryHeap(AbstractHeap): + """Binary Heap Class""" + def __init__(self): - self.currentSize = 0 + self.current_size = 0 self.heap = [(0)] def perc_up(self, i): @@ -62,34 +73,32 @@ def perc_up(self, i): self.heap[i], self.heap[i//2] = self.heap[i//2], self.heap[i] i = i // 2 - """ + def insert(self, val): + """ Method insert always start by inserting the element at the bottom. It inserts rightmost spot so as to maintain the complete tree property. Then, it fixes the tree by swapping the new element with its parent, until it finds an appropriate spot for the element. It essentially perc_up the minimum element Complexity: O(logN) - """ - def insert(self, val): + """ self.heap.append(val) - self.currentSize = self.currentSize + 1 - self.perc_up(self.currentSize) + self.current_size = self.current_size + 1 + self.perc_up(self.current_size) - """ + """ Method min_child returns the index of smaller of 2 children of parent at index i - """ + """ + def min_child(self, i): - if 2 * i + 1 > self.currentSize: # No right child + if 2 * i + 1 > self.current_size: # No right child return 2 * i - else: - # left child > right child - if self.heap[2 * i] > self.heap[2 * i +1]: - return 2 * i + 1 - else: - return 2 * i + if self.heap[2 * i] > self.heap[2 * i + 1]: + return 2 * i + 1 + return 2 * i def perc_down(self, i): - while 2 * i < self.currentSize: + while 2 * i < self.current_size: min_child = self.min_child(i) if self.heap[min_child] < self.heap[i]: # Swap min child with parent @@ -102,10 +111,12 @@ def perc_down(self, i): min heap property is restored Complexity: O(logN) """ + def remove_min(self): ret = self.heap[1] # the smallest value at beginning - self.heap[1] = self.heap[self.currentSize] # Replace it by the last value - self.currentSize = self.currentSize - 1 + # Replace it by the last value + self.heap[1] = self.heap[self.current_size] + self.current_size = self.current_size - 1 self.heap.pop() self.perc_down(1) return ret diff --git a/algorithms/heap/k_closest_points.py b/algorithms/heap/k_closest_points.py index 8e38e05e5..6a4e03604 100644 --- a/algorithms/heap/k_closest_points.py +++ b/algorithms/heap/k_closest_points.py @@ -2,8 +2,10 @@ Idea: Maintain a max heap of k elements. We can iterate through all points. -If a point p has a smaller distance to the origin than the top element of a heap, we add point p to the heap and remove the top element. -After iterating through all points, our heap contains the k closest points to the origin. +If a point p has a smaller distance to the origin than the top element of a +heap, we add point p to the heap and remove the top element. +After iterating through all points, our heap contains the k closest points to +the origin. """ @@ -14,7 +16,8 @@ def k_closest(points, k, origin=(0, 0)): # Time: O(k+(n-k)logk) # Space: O(k) """Initialize max heap with first k points. - Python does not support a max heap; thus we can use the default min heap where the keys (distance) are negated. + Python does not support a max heap; thus we can use the default min heap + where the keys (distance) are negated. """ heap = [(-distance(p, origin), p) for p in points[:k]] heapify(heap) @@ -24,10 +27,10 @@ def k_closest(points, k, origin=(0, 0)): check if p is smaller than the root of the max heap; if it is, add p to heap and remove root. Reheapify. """ - for p in points[k:]: - d = distance(p, origin) + for point in points[k:]: + dist = distance(point, origin) - heappushpop(heap, (-d, p)) # heappushpop does conditional check + heappushpop(heap, (-dist, point)) # heappushpop does conditional check """Same as: if d < -heap[0][0]: heappush(heap, (-d,p)) @@ -37,8 +40,9 @@ def k_closest(points, k, origin=(0, 0)): Each heappushpop call takes O(logk) time. """ - return [p for nd, p in heap] # return points in heap + return [point for nd, point in heap] # return points in heap def distance(point, origin=(0, 0)): + """ Calculates the distance for a point from origo""" return (point[0] - origin[0])**2 + (point[1] - origin[1])**2 diff --git a/algorithms/heap/merge_sorted_k_lists.py b/algorithms/heap/merge_sorted_k_lists.py index 2fbfe1df2..f3600c447 100644 --- a/algorithms/heap/merge_sorted_k_lists.py +++ b/algorithms/heap/merge_sorted_k_lists.py @@ -9,28 +9,32 @@ # Definition for singly-linked list. class ListNode(object): - def __init__(self, x): - self.val = x + """ ListNode Class""" + + def __init__(self, val): + self.val = val self.next = None def merge_k_lists(lists): + """ Merge Lists """ dummy = node = ListNode(0) - h = [(n.val, n) for n in lists if n] - heapify(h) - while h: - v, n = h[0] - if n.next is None: - heappop(h) # only change heap size when necessary + list_h = [(n.val, n) for n in lists if n] + heapify(list_h) + while list_h: + _, n_val = list_h[0] + if n_val.next is None: + heappop(list_h) # only change heap size when necessary else: - heapreplace(h, (n.next.val, n.next)) - node.next = n + heapreplace(list_h, (n_val.next.val, n_val.next)) + node.next = n_val node = node.next return dummy.next def merge_k_lists(lists): + """ Merge List """ dummy = ListNode(None) curr = dummy q = PriorityQueue() diff --git a/algorithms/maths/__init__.py b/algorithms/maths/__init__.py index ab58af9a5..8a3a9e627 100644 --- a/algorithms/maths/__init__.py +++ b/algorithms/maths/__init__.py @@ -1,3 +1,6 @@ +""" +Collection of mathematical algorithms and functions. +""" from .base_conversion import * from .decimal_to_binary_ip import * from .euler_totient import * @@ -22,4 +25,3 @@ from .power import * from .magic_number import * from .krishnamurthy_number import * - diff --git a/algorithms/maths/base_conversion.py b/algorithms/maths/base_conversion.py index 5d2a2214e..6badf749e 100644 --- a/algorithms/maths/base_conversion.py +++ b/algorithms/maths/base_conversion.py @@ -1,50 +1,49 @@ """ Integer base conversion algorithm -int2base(5, 2) return '101'. -base2int('F', 16) return 15. +int_to_base(5, 2) return '101'. +base_to_int('F', 16) return 15. """ import string -def int_to_base(n, base): +def int_to_base(num, base): """ - :type n: int + :type num: int :type base: int :rtype: str """ is_negative = False - if n == 0: + if num == 0: return '0' - elif n < 0: + if num < 0: is_negative = True - n *= -1 + num *= -1 digit = string.digits + string.ascii_uppercase res = '' - while n > 0: - res += digit[n % base] - n //= base + while num > 0: + res += digit[num % base] + num //= base if is_negative: return '-' + res[::-1] - else: - return res[::-1] + return res[::-1] -def base_to_int(s, base): +def base_to_int(str_to_convert, base): """ Note : You can use int() built-in function instead of this. - :type s: str + :type str_to_convert: str :type base: int :rtype: int """ - + digit = {} - for i,c in enumerate(string.digits + string.ascii_uppercase): - digit[c] = i + for ind, char in enumerate(string.digits + string.ascii_uppercase): + digit[char] = ind multiplier = 1 res = 0 - for c in s[::-1]: - res += digit[c] * multiplier + for char in str_to_convert[::-1]: + res += digit[char] * multiplier multiplier *= base return res diff --git a/algorithms/maths/chinese_remainder_theorem.py b/algorithms/maths/chinese_remainder_theorem.py index 91d9d1d9a..256e60f16 100644 --- a/algorithms/maths/chinese_remainder_theorem.py +++ b/algorithms/maths/chinese_remainder_theorem.py @@ -1,46 +1,46 @@ -from algorithms.maths.gcd import gcd +""" +Solves system of equations using the chinese remainder theorem if possible. +""" from typing import List +from algorithms.maths.gcd import gcd -def solve_chinese_remainder(num : List[int], rem : List[int]): +def solve_chinese_remainder(nums : List[int], rems : List[int]): """ Computes the smallest x that satisfies the chinese remainder theorem for a system of equations. The system of equations has the form: - x % num[0] = rem[0] - x % num[1] = rem[1] + x % nums[0] = rems[0] + x % nums[1] = rems[1] ... - x % num[k - 1] = rem[k - 1] - Where k is the number of elements in num and rem, k > 0. - All numbers in num needs to be pariwise coprime otherwise an exception is raised + x % nums[k - 1] = rems[k - 1] + Where k is the number of elements in nums and rems, k > 0. + All numbers in nums needs to be pariwise coprime otherwise an exception is raised returns x: the smallest value for x that satisfies the system of equations """ - if not len(num) == len(rem): - raise Exception("num and rem should have equal length") - if not len(num) > 0: - raise Exception("Lists num and rem need to contain at least one element") - for n in num: - if not n > 1: - raise Exception("All numbers in num needs to be > 1") - if not _check_coprime(num): - raise Exception("All pairs of numbers in num are not coprime") - k = len(num) + if not len(nums) == len(rems): + raise Exception("nums and rems should have equal length") + if not len(nums) > 0: + raise Exception("Lists nums and rems need to contain at least one element") + for num in nums: + if not num > 1: + raise Exception("All numbers in nums needs to be > 1") + if not _check_coprime(nums): + raise Exception("All pairs of numbers in nums are not coprime") + k = len(nums) x = 1 while True: i = 0 while i < k: - if x % num[i] != rem[i]: + if x % nums[i] != rems[i]: break i += 1 if i == k: return x - else: - x += 1 + x += 1 -def _check_coprime(l : List[int]): - for i in range(len(l)): - for j in range(len(l)): - if i == j: - continue - if gcd(l[i], l[j]) != 1: +def _check_coprime(list_to_check : List[int]): + for ind, num in enumerate(list_to_check): + for num2 in list_to_check[ind + 1:]: + if gcd(num, num2) != 1: return False return True diff --git a/algorithms/maths/combination.py b/algorithms/maths/combination.py index 308b0bcc4..998536056 100644 --- a/algorithms/maths/combination.py +++ b/algorithms/maths/combination.py @@ -1,9 +1,11 @@ +""" +Functions to calculate nCr (ie how many ways to choose r items from n items) +""" def combination(n, r): """This function calculates nCr.""" if n == r or r == 0: return 1 - else: - return combination(n-1, r-1) + combination(n-1, r) + return combination(n-1, r-1) + combination(n-1, r) def combination_memo(n, r): """This function calculates nCr using memoization method.""" diff --git a/algorithms/maths/cosine_similarity.py b/algorithms/maths/cosine_similarity.py index 831e3449f..3ee7fcdd8 100644 --- a/algorithms/maths/cosine_similarity.py +++ b/algorithms/maths/cosine_similarity.py @@ -13,29 +13,30 @@ def _l2_distance(vec): Calculate l2 distance from two given vectors. """ norm = 0. - for e in vec: - norm += e * e + for element in vec: + norm += element * element norm = math.sqrt(norm) return norm -def cosine_similarity(a, b): +def cosine_similarity(vec1, vec2): """ Calculate cosine similarity between given two vectors - :type a: list - :type b: list + :type vec1: list + :type vec2: list """ - if len(a) != len(b): - raise ValueError("The two vectors must be the same length. Got shape " + str(len(a)) + " and " + str(len(b))) + if len(vec1) != len(vec2): + raise ValueError("The two vectors must be the same length. Got shape " + str(len(vec1)) + + " and " + str(len(vec2))) - norm_a = _l2_distance(a) - norm_b = _l2_distance(b) + norm_a = _l2_distance(vec1) + norm_b = _l2_distance(vec2) similarity = 0. # Calculate the dot product of two vectors - for ae, be in zip(a, b): - similarity += ae * be + for vec1_element, vec2_element in zip(vec1, vec2): + similarity += vec1_element * vec2_element similarity /= (norm_a * norm_b) diff --git a/algorithms/maths/decimal_to_binary_ip.py b/algorithms/maths/decimal_to_binary_ip.py index 579e3402a..04e65e83c 100644 --- a/algorithms/maths/decimal_to_binary_ip.py +++ b/algorithms/maths/decimal_to_binary_ip.py @@ -7,6 +7,11 @@ """ def decimal_to_binary_util(val): + """ + Convert 8-bit decimal number to binary representation + :type val: str + :rtype: str + """ bits = [128, 64, 32, 16, 8, 4, 2, 1] val = int(val) binary_rep = '' @@ -20,6 +25,9 @@ def decimal_to_binary_util(val): return binary_rep def decimal_to_binary_ip(ip): + """ + Convert dotted-decimal ip address to binary representation with help of decimal_to_binary_util + """ values = ip.split('.') binary_list = [] for val in values: diff --git a/algorithms/maths/diffie_hellman_key_exchange.py b/algorithms/maths/diffie_hellman_key_exchange.py index 91f4e8229..0d8b751fd 100644 --- a/algorithms/maths/diffie_hellman_key_exchange.py +++ b/algorithms/maths/diffie_hellman_key_exchange.py @@ -1,3 +1,6 @@ +""" +Algorithms for performing diffie-hellman key exchange. +""" import math from random import randint @@ -6,20 +9,20 @@ Code from /algorithms/maths/prime_check.py, written by 'goswami-rahul' and 'Hai Honag Dang' """ -def prime_check(n): - """Return True if n is a prime number +def prime_check(num): + """Return True if num is a prime number Else return False. """ - if n <= 1: + if num <= 1: return False - if n == 2 or n == 3: + if num == 2 or num == 3: return True - if n % 2 == 0 or n % 3 == 0: + if num % 2 == 0 or num % 3 == 0: return False j = 5 - while j * j <= n: - if n % j == 0 or n % (j + 2) == 0: + while j * j <= num: + if num % j == 0 or num % (j + 2) == 0: return False j += 6 return True @@ -29,22 +32,19 @@ def prime_check(n): For positive integer n and given integer a that satisfies gcd(a, n) = 1, the order of a modulo n is the smallest positive integer k that satisfies pow (a, k) % n = 1. In other words, (a^k) ≡ 1 (mod n). -Order of certain number may or may not be exist. If so, return -1. +Order of certain number may or may not exist. If not, return -1. """ def find_order(a, n): - if ((a == 1) & (n == 1)): + if (a == 1) & (n == 1): + # Exception Handeling : 1 is the order of of 1 return 1 - """ Exception Handeling : - 1 is the order of of 1 """ - else: - if (math.gcd(a, n) != 1): - print("a and n should be relative prime!") - return -1 - else: - for i in range(1, n): - if (pow(a, i) % n == 1): - return i - return -1 + if math.gcd(a, n) != 1: + print ("a and n should be relative prime!") + return -1 + for i in range(1, n): + if pow(a, i) % n == 1: + return i + return -1 """ @@ -67,7 +67,6 @@ def euler_totient(n): result -= result // n return result - """ For positive integer n and given integer a that satisfies gcd(a, n) = 1, a is the primitive root of n, if a's order k for n satisfies k = ϕ(n). @@ -76,26 +75,20 @@ def euler_totient(n): """ def find_primitive_root(n): - if (n == 1): + """ Returns all primitive roots of n. """ + if n == 1: + # Exception Handeling : 0 is the only primitive root of 1 return [0] - """ Exception Handeling : - 0 is the only primitive root of 1 """ - else: - phi = euler_totient(n) - p_root_list = [] - """ It will return every primitive roots of n. """ - for i in range(1, n): - if (math.gcd(i, n) != 1): - continue - """ To have order, a and n must be - relative prime with each other. """ - else: - order = find_order(i, n) - if (order == phi): - p_root_list.append(i) - else: - continue - return p_root_list + phi = euler_totient(n) + p_root_list = [] + for i in range (1, n): + if math.gcd(i, n) != 1: + # To have order, a and n must be relative prime with each other. + continue + order = find_order(i, n) + if order == phi: + p_root_list.append(i) + return p_root_list """ @@ -152,42 +145,40 @@ def bob_shared_key(a_pu_k, b_pr_k, p): return pow(a_pu_k, b_pr_k) % p -def diffie_hellman_key_exchange(a, p, option=None): - if (option is not None): +def diffie_hellman_key_exchange(a, p, option = None): + """ Perform diffie-helmman key exchange. """ + if option is not None: + # Print explanation of process when option parameter is given option = 1 - """ Print explanation of process - when option parameter is given """ - if (prime_check(p) is False): - print("%d is not a prime number" % p) + if prime_check(p) is False: + print(f"{p} is not a prime number") + # p must be large prime number + return False + try: + p_root_list = find_primitive_root(p) + p_root_list.index(a) + except ValueError: + print(f"{a} is not a primitive root of {p}") + # a must be primitive root of p return False - """p must be large prime number""" - else: - try: - p_root_list = find_primitive_root(p) - p_root_list.index(a) - except ValueError: - print("%d is not a primitive root of %d" % (a, p)) - return False - """ a must be primitive root of p """ - a_pr_k = alice_private_key(p) - a_pu_k = alice_public_key(a_pr_k, a, p) + a_pr_k = alice_private_key(p) + a_pu_k = alice_public_key(a_pr_k, a, p) - b_pr_k = bob_private_key(p) - b_pu_k = bob_public_key(b_pr_k, a, p) + b_pr_k = bob_private_key(p) + b_pu_k = bob_public_key(b_pr_k, a, p) - if (option == 1): - print("Private key of Alice = %d" % a_pr_k) - print("Public key of Alice = %d" % a_pu_k) - print("Private key of Bob = %d" % b_pr_k) - print("Public key of Bob = %d" % b_pu_k) + if option == 1: + print(f"Alice's private key: {a_pr_k}") + print(f"Alice's public key: {a_pu_k}") + print(f"Bob's private key: {b_pr_k}") + print(f"Bob's public key: {b_pu_k}") - """ In here, Alice send her public key to Bob, - and Bob also send his public key to Alice.""" + # In here, Alice send her public key to Bob, and Bob also send his public key to Alice. - a_sh_k = alice_shared_key(b_pu_k, a_pr_k, p) - b_sh_k = bob_shared_key(a_pu_k, b_pr_k, p) - print("Shared key calculated by Alice = %d" % a_sh_k) - print("Shared key calculated by Bob = %d" % b_sh_k) + a_sh_k = alice_shared_key(b_pu_k, a_pr_k, p) + b_sh_k = bob_shared_key(a_pu_k, b_pr_k, p) + print (f"Shared key calculated by Alice = {a_sh_k}") + print ("Shared key calculated by Bob = {b_sh_k}") - return (a_sh_k == b_sh_k) + return a_sh_k == b_sh_k diff --git a/algorithms/maths/euler_totient.py b/algorithms/maths/euler_totient.py index 047b72827..f29d382ff 100644 --- a/algorithms/maths/euler_totient.py +++ b/algorithms/maths/euler_totient.py @@ -7,12 +7,12 @@ def euler_totient(n): """Euler's totient function or Phi function. Time Complexity: O(sqrt(n)).""" - result = n; + result = n for i in range(2, int(n ** 0.5) + 1): if n % i == 0: while n % i == 0: n //= i result -= result // i if n > 1: - result -= result // n; - return result; + result -= result // n + return result diff --git a/algorithms/maths/extended_gcd.py b/algorithms/maths/extended_gcd.py index 83b657807..a0e9cbc12 100644 --- a/algorithms/maths/extended_gcd.py +++ b/algorithms/maths/extended_gcd.py @@ -1,19 +1,24 @@ -def extended_gcd(a, b): +""" +Provides extended GCD functionality for finding co-prime numbers s and t such that: +num1 * s + num2 * t = GCD(num1, num2). +Ie the coefficients of Bézout's identity. +""" +def extended_gcd(num1, num2): """Extended GCD algorithm. Return s, t, g - such that a * s + b * t = GCD(a, b) + such that num1 * s + num2 * t = GCD(num1, num2) and s and t are co-prime. """ old_s, s = 1, 0 old_t, t = 0, 1 - old_r, r = a, b - + old_r, r = num1, num2 + while r != 0: quotient = old_r / r - + old_r, r = r, old_r - quotient * r old_s, s = s, old_s - quotient * s old_t, t = t, old_t - quotient * t - + return old_s, old_t, old_r diff --git a/algorithms/maths/factorial.py b/algorithms/maths/factorial.py index 23e1f5354..f5e3ea5a2 100644 --- a/algorithms/maths/factorial.py +++ b/algorithms/maths/factorial.py @@ -1,3 +1,6 @@ +""" +Calculates the factorial with the added functionality of calculating it modulo mod. +""" def factorial(n, mod=None): """Calculates factorial iteratively. If mod is not None, then return (n! % mod) diff --git a/algorithms/maths/find_order_simple.py b/algorithms/maths/find_order_simple.py index d4866ee8c..8f69773c7 100644 --- a/algorithms/maths/find_order_simple.py +++ b/algorithms/maths/find_order_simple.py @@ -1,27 +1,27 @@ -import math - """ For positive integer n and given integer a that satisfies gcd(a, n) = 1, the order of a modulo n is the smallest positive integer k that satisfies pow (a, k) % n = 1. In other words, (a^k) ≡ 1 (mod n). -Order of certain number may or may not be exist. If so, return -1. +Order of a certain number may or may not be exist. If not, return -1. + +Total time complexity O(nlog(n)): +O(n) for iteration loop, +O(log(n)) for built-in power function """ + +import math + def find_order(a, n): - if ((a == 1) & (n == 1)): + """ + Find order for positive integer n and given integer a that satisfies gcd(a, n) = 1. + """ + if (a == 1) & (n == 1): + # Exception Handeling : 1 is the order of of 1 return 1 - """ Exception Handeling : - 1 is the order of of 1 """ - else: - if (math.gcd(a, n) != 1): - print ("a and n should be relative prime!") - return -1 - else: - for i in range(1, n): - if (pow(a, i) % n == 1): - return i - return -1 - -""" -Time complexity only for calculating order = O(nlog(n)) -O(n) for iteration loop, O(log(n)) for built-in power function -""" + if math.gcd(a, n) != 1: + print ("a and n should be relative prime!") + return -1 + for i in range(1, n): + if pow(a, i) % n == 1: + return i + return -1 diff --git a/algorithms/maths/find_primitive_root_simple.py b/algorithms/maths/find_primitive_root_simple.py index a5b74f6a6..366f40191 100644 --- a/algorithms/maths/find_primitive_root_simple.py +++ b/algorithms/maths/find_primitive_root_simple.py @@ -1,3 +1,6 @@ +""" +Function to find the primitive root of a number. +""" import math """ @@ -7,19 +10,20 @@ Order of certain number may or may not be exist. If so, return -1. """ def find_order(a, n): - if ((a == 1) & (n == 1)): + """ + Find order for positive integer n and given integer a that satisfies gcd(a, n) = 1. + Time complexity O(nlog(n)) + """ + if (a == 1) & (n == 1): + # Exception Handeling : 1 is the order of of 1 return 1 - """ Exception Handeling : - 1 is the order of of 1 """ - else: - if (math.gcd(a, n) != 1): - print ("a and n should be relative prime!") - return -1 - else: - for i in range(1, n): - if (pow(a, i) % n == 1): - return i - return -1 + if math.gcd(a, n) != 1: + print ("a and n should be relative prime!") + return -1 + for i in range(1, n): + if pow(a, i) % n == 1: + return i + return -1 """ Euler's totient function, also known as phi-function ϕ(n), @@ -31,41 +35,33 @@ def find_order(a, n): def euler_totient(n): """Euler's totient function or Phi function. Time Complexity: O(sqrt(n)).""" - result = n; + result = n for i in range(2, int(n ** 0.5) + 1): if n % i == 0: while n % i == 0: n //= i result -= result // i if n > 1: - result -= result // n; - return result; + result -= result // n + return result """ For positive integer n and given integer a that satisfies gcd(a, n) = 1, a is the primitive root of n, if a's order k for n satisfies k = ϕ(n). -Primitive roots of certain number may or may not be exist. +Primitive roots of certain number may or may not exist. If so, return empty list. """ - def find_primitive_root(n): - if (n == 1): + if n == 1: + # Exception Handeling : 0 is the only primitive root of 1 return [0] - """ Exception Handeling : - 0 is the only primitive root of 1 """ - else: - phi = euler_totient(n) - p_root_list = [] - """ It will return every primitive roots of n. """ - for i in range (1, n): - if (math.gcd(i, n) != 1): - continue - """ To have order, a and n must be - relative prime with each other. """ - else: - order = find_order(i, n) - if (order == phi): - p_root_list.append(i) - else: - continue - return p_root_list + phi = euler_totient(n) + p_root_list = [] + """ It will return every primitive roots of n. """ + for i in range (1, n): + #To have order, a and n must be relative prime with each other. + if math.gcd(i, n) == 1: + order = find_order(i, n) + if order == phi: + p_root_list.append(i) + return p_root_list diff --git a/algorithms/maths/gcd.py b/algorithms/maths/gcd.py index 35af7f118..189eae100 100644 --- a/algorithms/maths/gcd.py +++ b/algorithms/maths/gcd.py @@ -1,3 +1,8 @@ +""" +Functions for calculating the greatest common divisor of two integers or +their least common multiple. +""" + def gcd(a, b): """Computes the greatest common divisor of integers a and b using Euclid's Algorithm. @@ -18,12 +23,10 @@ def gcd(a, b): a, b = b, a % b return a - def lcm(a, b): """Computes the lowest common multiple of integers a and b.""" return abs(a) * abs(b) / gcd(a, b) - """ Given a positive integer x, computes the number of trailing zero of x. Example @@ -35,20 +38,19 @@ def lcm(a, b): ~~~^^^ Output : 3 """ - def trailing_zero(x): - cnt = 0 + count = 0 while x and not x & 1: - cnt += 1 + count += 1 x >>= 1 - return cnt + return count """ Given two non-negative integer a and b, computes the greatest common divisor of a and b using bitwise operator. """ - def gcd_bit(a, b): + """ Similar to gcd but uses bitwise operators and less error handling.""" tza = trailing_zero(a) tzb = trailing_zero(b) a >>= tza @@ -59,5 +61,3 @@ def gcd_bit(a, b): a -= b a >>= trailing_zero(a) return a << min(tza, tzb) - - diff --git a/algorithms/maths/generate_strobogrammtic.py b/algorithms/maths/generate_strobogrammtic.py index dd0c400c4..d051865fb 100644 --- a/algorithms/maths/generate_strobogrammtic.py +++ b/algorithms/maths/generate_strobogrammtic.py @@ -8,15 +8,14 @@ Given n = 2, return ["11","69","88","96"]. """ - def gen_strobogrammatic(n): """ + Given n, generate all strobogrammatic numbers of length n. :type n: int :rtype: List[str] """ return helper(n, n) - def helper(n, length): if n == 0: return [""] @@ -33,7 +32,6 @@ def helper(n, length): result.append("6" + middle + "9") return result - def strobogrammatic_in_range(low, high): """ :type low: str @@ -49,13 +47,11 @@ def strobogrammatic_in_range(low, high): for perm in res: if len(perm) == low_len and int(perm) < int(low): continue - elif len(perm) == high_len and int(perm) > int(high): + if len(perm) == high_len and int(perm) > int(high): continue - else: - count += 1 + count += 1 return count - def helper2(n, length): if n == 0: return [""] diff --git a/algorithms/maths/hailstone.py b/algorithms/maths/hailstone.py index 22321232b..77261ac5e 100644 --- a/algorithms/maths/hailstone.py +++ b/algorithms/maths/hailstone.py @@ -1,13 +1,21 @@ +""" +Implementation of hailstone function which generates a sequence for some n by following these rules: +* n == 1 : done +* n is even : the next n = n/2 +* n is odd : the next n = 3n + 1 +""" + def hailstone(n): - """Return the 'hailstone sequence' from n to 1 - n: The starting point of the hailstone sequence - """ + """ + Return the 'hailstone sequence' from n to 1 + n: The starting point of the hailstone sequence + """ - sequence = [n] - while n > 1: - if n%2 != 0: - n = 3*n + 1 - else: - n = int(n/2) - sequence.append(n) - return sequence \ No newline at end of file + sequence = [n] + while n > 1: + if n%2 != 0: + n = 3*n + 1 + else: + n = int(n/2) + sequence.append(n) + return sequence diff --git a/algorithms/maths/is_strobogrammatic.py b/algorithms/maths/is_strobogrammatic.py index 018e6955d..b849ddac4 100644 --- a/algorithms/maths/is_strobogrammatic.py +++ b/algorithms/maths/is_strobogrammatic.py @@ -18,8 +18,7 @@ def is_strobogrammatic(num): i = 0 j = len(num) - 1 while i <= j: - x = comb.find(num[i]+num[j]) - if x == -1: + if comb.find(num[i]+num[j]) == -1: return False i += 1 j -= 1 diff --git a/algorithms/maths/krishnamurthy_number.py b/algorithms/maths/krishnamurthy_number.py index a0a451ee4..1d41721b1 100644 --- a/algorithms/maths/krishnamurthy_number.py +++ b/algorithms/maths/krishnamurthy_number.py @@ -1,7 +1,8 @@ """ -A Krishnamurthy number is a number whose sum total of the factorials of each digit is equal to the number itself. +A Krishnamurthy number is a number whose sum total of the factorials of each digit is equal to the +number itself. -Here's what I mean by that: +The following are some examples of Krishnamurthy numbers: "145" is a Krishnamurthy Number because, 1! + 4! + 5! = 1 + 24 + 120 = 145 @@ -12,11 +13,13 @@ "357" or "25965" is NOT a Krishnamurthy Number 3! + 5! + 7! = 6 + 120 + 5040 != 357 -The following function will check if a number is a Krishnamurthy Number or not and return a boolean value. +The following function will check if a number is a Krishnamurthy Number or not and return a +boolean value. """ def find_factorial(n): + """ Calculates the factorial of a given number n """ fact = 1 while n != 0: fact *= n @@ -40,4 +43,4 @@ def krishnamurthy_number(n): temp //= 10 # returns True if number is krishnamurthy - return (sum_of_digits == n) + return sum_of_digits == n diff --git a/algorithms/maths/magic_number.py b/algorithms/maths/magic_number.py index fe62bc680..6107753d2 100644 --- a/algorithms/maths/magic_number.py +++ b/algorithms/maths/magic_number.py @@ -1,8 +1,8 @@ -"""Magic Number +""" +Magic Number A number is said to be a magic number, -if the sum of its digits are calculated till a single digit recursively -by adding the sum of the digits after every addition. -If the single digit comes out to be 1,then the number is a magic number. +if summing the digits of the number and then recursively repeating this process for the given sum +untill the number becomes a single digit number equal to 1. Example: Number = 50113 => 5+0+1+1+3=10 => 1+0=1 [This is a Magic Number] @@ -13,15 +13,14 @@ The following function checks for Magic numbers and returns a Boolean accordingly. """ - def magic_number(n): + """ Checks if n is a magic number """ total_sum = 0 # will end when n becomes 0 # AND # sum becomes single digit. while n > 0 or total_sum > 9: - # when n becomes 0 but we have a total_sum, # we update the value of n with the value of the sum digits if n == 0: @@ -32,5 +31,3 @@ def magic_number(n): # Return true if sum becomes 1 return total_sum == 1 - - diff --git a/algorithms/maths/modular_inverse.py b/algorithms/maths/modular_inverse.py index f53d0f64f..c6f849d7d 100644 --- a/algorithms/maths/modular_inverse.py +++ b/algorithms/maths/modular_inverse.py @@ -1,5 +1,5 @@ -# extended_gcd(a, b) modified from -# https://github.com/keon/algorithms/blob/master/algorithms/maths/extended_gcd.py +# extended_gcd(a, b) modified from +# https://github.com/keon/algorithms/blob/master/algorithms/maths/extended_gcd.py def extended_gcd(a: int, b: int) -> [int, int, int]: """Extended GCD algorithm. @@ -11,24 +11,24 @@ def extended_gcd(a: int, b: int) -> [int, int, int]: old_s, s = 1, 0 old_t, t = 0, 1 old_r, r = a, b - + while r != 0: quotient = old_r // r - + old_r, r = r, old_r - quotient * r old_s, s = s, old_s - quotient * s old_t, t = t, old_t - quotient * t - + return old_s, old_t, old_r def modular_inverse(a: int, m: int) -> int: """ Returns x such that a * x = 1 (mod m) - a and m must be coprime + a and m must be coprime """ - s, t, g = extended_gcd(a, m) + s, _, g = extended_gcd(a, m) if g != 1: raise ValueError("a and m must be coprime") return s % m diff --git a/algorithms/maths/next_bigger.py b/algorithms/maths/next_bigger.py index e14e22cb8..a159da197 100644 --- a/algorithms/maths/next_bigger.py +++ b/algorithms/maths/next_bigger.py @@ -1,9 +1,9 @@ """ -I just bombed an interview and made pretty much zero -progress on my interview question. +I just bombed an interview and made pretty much zero +progress on my interview question. -Given a number, find the next higher number which has the -exact same set of digits as the original number. +Given a number, find the next higher number which has the +exact same set of digits as the original number. For example: given 38276 return 38627. given 99999 return -1. (no such number exists) diff --git a/algorithms/maths/next_perfect_square.py b/algorithms/maths/next_perfect_square.py index 7e6e6b918..be2d24a24 100644 --- a/algorithms/maths/next_perfect_square.py +++ b/algorithms/maths/next_perfect_square.py @@ -1,6 +1,7 @@ """ This program will look for the next perfect square. -Check the argument to see if it is a perfect square itself, if it is not then return -1 otherwise look for the next perfect square +Check the argument to see if it is a perfect square itself, if it is not then return -1 otherwise +look for the next perfect square. for instance if you pass 121 then the script should return the next perfect square which is 144. """ @@ -9,11 +10,8 @@ def find_next_square(sq): if root.is_integer(): return (root + 1)**2 return -1 - - -# Another way: def find_next_square2(sq): - x = sq**0.5 - return -1 if x % 1 else (x+1)**2 - + """ Alternative method, works by evaluating anything non-zero as True (0.000001 --> True) """ + root = sq**0.5 + return -1 if root % 1 else (root+1)**2 diff --git a/algorithms/maths/nth_digit.py b/algorithms/maths/nth_digit.py index 381926f3e..f6454e940 100644 --- a/algorithms/maths/nth_digit.py +++ b/algorithms/maths/nth_digit.py @@ -14,4 +14,4 @@ def find_nth_digit(n): start *= 10 start += (n-1) / length s = str(start) - return int(s[(n-1) % length]) \ No newline at end of file + return int(s[(n-1) % length]) diff --git a/algorithms/maths/num_digits.py b/algorithms/maths/num_digits.py index 93f0e20f3..4ecd5c7c3 100644 --- a/algorithms/maths/num_digits.py +++ b/algorithms/maths/num_digits.py @@ -1,11 +1,12 @@ """ - num_digits() method will return the number of digits of a number in O(1) time using math.log10() method. +num_digits() method will return the number of digits of a number in O(1) time using +math.log10() method. """ import math def num_digits(n): - n=abs(n) - if(n==0): - return 1; - return int(math.log10(n))+1 + n=abs(n) + if n==0: + return 1 + return int(math.log10(n))+1 diff --git a/algorithms/maths/power.py b/algorithms/maths/power.py index 99e694faf..70d8587de 100644 --- a/algorithms/maths/power.py +++ b/algorithms/maths/power.py @@ -1,10 +1,14 @@ -def power(a: int, n: int, r: int = None): +""" +Performs exponentiation, similarly to the built-in pow() or ** functions. +Allows also for calculating the exponentiation modulo. +""" +def power(a: int, n: int, mod: int = None): """ Iterative version of binary exponentiation - + Calculate a ^ n - if r is specified, return the result modulo r - + if mod is specified, return the result modulo mod + Time Complexity : O(log(n)) Space Complexity : O(1) """ @@ -13,20 +17,20 @@ def power(a: int, n: int, r: int = None): if n & 1: ans = ans * a a = a * a - if r: - ans %= r - a %= r + if mod: + ans %= mod + a %= mod n >>= 1 return ans -def power_recur(a: int, n: int, r: int = None): +def power_recur(a: int, n: int, mod: int = None): """ Recursive version of binary exponentiation - + Calculate a ^ n - if r is specified, return the result modulo r - + if mod is specified, return the result modulo mod + Time Complexity : O(log(n)) Space Complexity : O(log(n)) """ @@ -35,11 +39,10 @@ def power_recur(a: int, n: int, r: int = None): elif n == 1: ans = a else: - ans = power_recur(a, n // 2, r) + ans = power_recur(a, n // 2, mod) ans = ans * ans if n % 2: ans = ans * a - if r: - ans %= r + if mod: + ans %= mod return ans - diff --git a/algorithms/maths/primes_sieve_of_eratosthenes.py b/algorithms/maths/primes_sieve_of_eratosthenes.py index 7c21c6d82..b0d1d96c5 100644 --- a/algorithms/maths/primes_sieve_of_eratosthenes.py +++ b/algorithms/maths/primes_sieve_of_eratosthenes.py @@ -24,7 +24,6 @@ and complexity it's also a half now. """ - def get_primes(n): """Return list of all primes less than n, Using sieve of Eratosthenes. diff --git a/algorithms/maths/pythagoras.py b/algorithms/maths/pythagoras.py index d89626039..b24b682ac 100644 --- a/algorithms/maths/pythagoras.py +++ b/algorithms/maths/pythagoras.py @@ -1,16 +1,20 @@ """ -input two of the three side in right angled triangle and return the third. use "?" to indicate the unknown side. +Given the lengths of two of the three sides of a right angled triangle, this function returns the +length of the third side. """ -def pythagoras(opposite,adjacent,hypotenuse): +def pythagoras(opposite, adjacent, hypotenuse): + """ + Returns length of a third side of a right angled triangle. + Passing "?" will indicate the unknown side. + """ try: if opposite == str("?"): return ("Opposite = " + str(((hypotenuse**2) - (adjacent**2))**0.5)) - elif adjacent == str("?"): + if adjacent == str("?"): return ("Adjacent = " + str(((hypotenuse**2) - (opposite**2))**0.5)) - elif hypotenuse == str("?"): + if hypotenuse == str("?"): return ("Hypotenuse = " + str(((opposite**2) + (adjacent**2))**0.5)) - else: - return "You already know the answer!" + return "You already know the answer!" except: - raise ValueError("invalid argument were given.") + raise ValueError("invalid argument(s) were given.") diff --git a/algorithms/maths/rabin_miller.py b/algorithms/maths/rabin_miller.py index a3aad8ef2..08b12c117 100644 --- a/algorithms/maths/rabin_miller.py +++ b/algorithms/maths/rabin_miller.py @@ -47,5 +47,5 @@ def valid_witness(a): for _ in range(k): if valid_witness(random.randrange(2, n - 2)): return False - + return True diff --git a/algorithms/maths/recursive_binomial_coefficient.py b/algorithms/maths/recursive_binomial_coefficient.py index ae57bd62d..a92420050 100644 --- a/algorithms/maths/recursive_binomial_coefficient.py +++ b/algorithms/maths/recursive_binomial_coefficient.py @@ -22,7 +22,5 @@ def recursive_binomial_coefficient(n,k): if k > n/2: #C(n,k) = C(n,n-k), so if n/2 is sufficiently small, we can reduce the problem size. return recursive_binomial_coefficient(n,n-k) - else: - #else, we know C(n,k) = (n/k)C(n-1,k-1), so we can use this to reduce our problem size. - return int((n/k)*recursive_binomial_coefficient(n-1,k-1)) - + #else, we know C(n,k) = (n/k)C(n-1,k-1), so we can use this to reduce our problem size. + return int((n/k)*recursive_binomial_coefficient(n-1,k-1)) diff --git a/algorithms/maths/rsa.py b/algorithms/maths/rsa.py index 70b7bc5cc..bbf193caa 100644 --- a/algorithms/maths/rsa.py +++ b/algorithms/maths/rsa.py @@ -21,6 +21,13 @@ (a ** b) % c == pow(a,b,c) """ +# sample usage: +# n,e,d = generate_key(16) +# data = 20 +# encrypted = pow(data,e,n) +# decrypted = pow(encrypted,d,n) +# assert decrypted == data + import random @@ -58,23 +65,23 @@ def is_prime(num): # size in bits of p and q need to add up to the size of n p_size = k / 2 q_size = k - p_size - + e = gen_prime(k, seed) # in many cases, e is also chosen to be a small constant - + while True: p = gen_prime(p_size, seed) if p % e != 1: break - + while True: q = gen_prime(q_size, seed) if q % e != 1: break - + n = p * q l = (p - 1) * (q - 1) # calculate totient function d = modinv(e, l) - + return int(n), int(e), int(d) @@ -84,13 +91,3 @@ def encrypt(data, e, n): def decrypt(data, d, n): return pow(int(data), int(d), int(n)) - - - -# sample usage: -# n,e,d = generate_key(16) -# data = 20 -# encrypted = pow(data,e,n) -# decrypted = pow(encrypted,d,n) -# assert decrypted == data - diff --git a/algorithms/maths/summing_digits.py b/algorithms/maths/summing_digits.py index f181a92eb..ec30ffda8 100644 --- a/algorithms/maths/summing_digits.py +++ b/algorithms/maths/summing_digits.py @@ -1,18 +1,19 @@ """ Recently, I encountered an interview question whose description was as below: -The number 89 is the first integer with more than one digit whose digits when raised up to consecutive powers give the same -number. For example, 89 = 8**1 + 9**2 gives the number 89. +The number 89 is the first integer with more than one digit whose digits when raised up to +consecutive powers give the same number. For example, 89 = 8**1 + 9**2 gives the number 89. The next number after 89 with this property is 135 = 1**1 + 3**2 + 5**3 = 135. -Write a function that returns a list of numbers with the above property. The function will receive range as parameter. +Write a function that returns a list of numbers with the above property. The function will +receive range as parameter. """ -def sum_dig_pow(a, b): +def sum_dig_pow(low, high): result = [] - - for number in range(a, b + 1): + + for number in range(low, high + 1): exponent = 1 # set to 1 summation = 0 # set to 1 number_as_string = str(number) diff --git a/algorithms/maths/symmetry_group_cycle_index.py b/algorithms/maths/symmetry_group_cycle_index.py index e6d01847e..01b3e05ee 100644 --- a/algorithms/maths/symmetry_group_cycle_index.py +++ b/algorithms/maths/symmetry_group_cycle_index.py @@ -1,9 +1,3 @@ -from polynomial import (Monomial, Polynomial) -from gcd import lcm -from fractions import Fraction -from typing import Dict, Union - - """ The significance of the cycle index (polynomial) of symmetry group is deeply rooted in counting the number of configurations @@ -56,16 +50,19 @@ Code: def solve(w, h, s): + s1 = get_cycle_index_sym(w) + s2 = get_cycle_index_sym(h) -s1 = get_cycle_index_sym(w) -s2 = get_cycle_index_sym(h) - -result = cycle_product_for_two_polynomials(s1, s2, s) - -return str(result) + result = cycle_product_for_two_polynomials(s1, s2, s) + return str(result) """ +from fractions import Fraction +from typing import Dict, Union +from polynomial import ( Monomial, Polynomial ) +from gcd import lcm + def cycle_product(m1: Monomial, m2: Monomial) -> Monomial: """ diff --git a/algorithms/search/__init__.py b/algorithms/search/__init__.py index 2226e4b5d..3f39479bc 100644 --- a/algorithms/search/__init__.py +++ b/algorithms/search/__init__.py @@ -1,3 +1,7 @@ +""" +Collection of search algorithms: finding the needle in a haystack. +""" + from .binary_search import * from .ternary_search import * from .first_occurrence import * diff --git a/algorithms/search/binary_search.py b/algorithms/search/binary_search.py index d8bccf06b..6b398764c 100644 --- a/algorithms/search/binary_search.py +++ b/algorithms/search/binary_search.py @@ -1,33 +1,51 @@ -# -# Binary search works for a sorted array. -# Note: The code logic is written for an array sorted in -# increasing order. -#For Binary Search, T(N) = T(N/2) + O(1) // the recurrence relation -#Apply Masters Theorem for computing Run time complexity of recurrence relations : T(N) = aT(N/b) + f(N) -#Here, a = 1, b = 2 => log (a base b) = 1 -# also, here f(N) = n^c log^k(n) //k = 0 & c = log (a base b) So, T(N) = O(N^c log^(k+1)N) = O(log(N)) +""" +Binary Search +Find an element in a sorted array (in ascending order). +""" + +# For Binary Search, T(N) = T(N/2) + O(1) // the recurrence relation +# Apply Masters Theorem for computing Run time complexity of recurrence relations: +# T(N) = aT(N/b) + f(N) +# Here, +# a = 1, b = 2 => log (a base b) = 1 +# also, here +# f(N) = n^c log^k(n) // k = 0 & c = log (a base b) +# So, +# T(N) = O(N^c log^(k+1)N) = O(log(N)) def binary_search(array, query): - lo, hi = 0, len(array) - 1 - while lo <= hi: - mid = (hi + lo) // 2 + """ + Worst-case Complexity: O(log(n)) + + reference: https://en.wikipedia.org/wiki/Binary_search_algorithm + """ + + low, high = 0, len(array) - 1 + while low <= high: + mid = (high + low) // 2 val = array[mid] if val == query: return mid - elif val < query: - lo = mid + 1 + + if val < query: + low = mid + 1 else: - hi = mid - 1 + high = mid - 1 return None def binary_search_recur(array, low, high, val): + """ + Worst-case Complexity: O(log(n)) + + reference: https://en.wikipedia.org/wiki/Binary_search_algorithm + """ + if low > high: # error case return -1 mid = (low + high) // 2 if val < array[mid]: return binary_search_recur(array, low, mid - 1, val) - elif val > array[mid]: + if val > array[mid]: return binary_search_recur(array, mid + 1, high, val) - else: - return mid + return mid diff --git a/algorithms/search/find_min_rotate.py b/algorithms/search/find_min_rotate.py index 1afc4eef2..b47fd4e87 100644 --- a/algorithms/search/find_min_rotate.py +++ b/algorithms/search/find_min_rotate.py @@ -7,6 +7,9 @@ You may assume no duplicate exists in the array. """ def find_min_rotate(array): + """ + Finds the minimum element in a sorted array that has been rotated. + """ low = 0 high = len(array) - 1 while low < high: @@ -19,10 +22,12 @@ def find_min_rotate(array): return array[low] def find_min_rotate_recur(array, low, high): + """ + Finds the minimum element in a sorted array that has been rotated. + """ mid = (low + high) // 2 if mid == low: return array[low] - elif array[mid] > array[high]: + if array[mid] > array[high]: return find_min_rotate_recur(array, mid + 1, high) - else: - return find_min_rotate_recur(array, low, mid) + return find_min_rotate_recur(array, low, mid) diff --git a/algorithms/search/first_occurrence.py b/algorithms/search/first_occurrence.py index 86dc89ced..119ba4df2 100644 --- a/algorithms/search/first_occurrence.py +++ b/algorithms/search/first_occurrence.py @@ -1,18 +1,23 @@ -# -# Find first occurance of a number in a sorted array (increasing order) -# Approach- Binary Search -# T(n)- O(log n) -# +""" +Find first occurance of a number in a sorted array (increasing order) +Approach- Binary Search +T(n)- O(log n) +""" def first_occurrence(array, query): - lo, hi = 0, len(array) - 1 - while lo <= hi: - mid = (lo + hi) // 2 + """ + Returns the index of the first occurance of the given element in an array. + The array has to be sorted in increasing order. + """ + + low, high = 0, len(array) - 1 + while low <= high: + mid = (low + high) // 2 #print("lo: ", lo, " hi: ", hi, " mid: ", mid) - if lo == hi: + if low == high: break if array[mid] < query: - lo = mid + 1 + low = mid + 1 else: - hi = mid - if array[lo] == query: - return lo + high = mid + if array[low] == query: + return low diff --git a/algorithms/search/interpolation_search.py b/algorithms/search/interpolation_search.py index 9227e1f75..5b1d00a1a 100644 --- a/algorithms/search/interpolation_search.py +++ b/algorithms/search/interpolation_search.py @@ -1,4 +1,4 @@ -""" +""" Python implementation of the Interpolation Search algorithm. Given a sorted array in increasing order, interpolation search calculates the starting point of its search according to the search key. @@ -41,14 +41,14 @@ def interpolation_search(array: List[int], search_key: int) -> int: pos = low + int(((search_key - array[low]) * (high - low) / (array[high] - array[low]))) - # search_key is found + # search_key is found if array[pos] == search_key: return pos # if search_key is larger, search_key is in upper part if array[pos] < search_key: low = pos + 1 - + # if search_key is smaller, search_key is in lower part else: high = pos - 1 diff --git a/algorithms/search/jump_search.py b/algorithms/search/jump_search.py index da8d8c32e..2ec074938 100644 --- a/algorithms/search/jump_search.py +++ b/algorithms/search/jump_search.py @@ -1,40 +1,45 @@ +""" +Jump Search + +Find an element in a sorted array. +""" + import math def jump_search(arr,target): - """Jump Search - Worst-case Complexity: O(√n) (root(n)) - All items in list must be sorted like binary search - - Find block that contains target value and search it linearly in that block - It returns a first target value in array + """ + Worst-case Complexity: O(√n) (root(n)) + All items in list must be sorted like binary search - reference: https://en.wikipedia.org/wiki/Jump_search + Find block that contains target value and search it linearly in that block + It returns a first target value in array + reference: https://en.wikipedia.org/wiki/Jump_search """ - n = len(arr) - block_size = int(math.sqrt(n)) + + length = len(arr) + block_size = int(math.sqrt(length)) block_prev = 0 block= block_size # return -1 means that array doesn't contain target value # find block that contains target value - - if arr[n - 1] < target: - return -1 - while block <= n and arr[block - 1] < target: + + if arr[length - 1] < target: + return -1 + while block <= length and arr[block - 1] < target: block_prev = block block += block_size # find target value in block - + while arr[block_prev] < target : block_prev += 1 - if block_prev == min(block, n) : + if block_prev == min(block, length) : return -1 # if there is target value in array, return it - + if arr[block_prev] == target : return block_prev - else : - return -1 + return -1 diff --git a/algorithms/search/last_occurrence.py b/algorithms/search/last_occurrence.py index 345b42395..6374625e6 100644 --- a/algorithms/search/last_occurrence.py +++ b/algorithms/search/last_occurrence.py @@ -1,16 +1,20 @@ -# -# Find last occurance of a number in a sorted array (increasing order) -# Approach- Binary Search -# T(n)- O(log n) -# +""" +Find last occurance of a number in a sorted array (increasing order) +Approach- Binary Search +T(n)- O(log n) +""" def last_occurrence(array, query): - lo, hi = 0, len(array) - 1 - while lo <= hi: - mid = (hi + lo) // 2 + """ + Returns the index of the last occurance of the given element in an array. + The array has to be sorted in increasing order. + """ + low, high = 0, len(array) - 1 + while low <= high: + mid = (high + low) // 2 if (array[mid] == query and mid == len(array)-1) or \ (array[mid] == query and array[mid+1] > query): return mid - elif (array[mid] <= query): - lo = mid + 1 + if array[mid] <= query: + low = mid + 1 else: - hi = mid - 1 + high = mid - 1 diff --git a/algorithms/search/linear_search.py b/algorithms/search/linear_search.py index cf57fcf97..d375d77f2 100644 --- a/algorithms/search/linear_search.py +++ b/algorithms/search/linear_search.py @@ -1,12 +1,15 @@ -# -# Linear search works in any array. -# -# T(n): O(n) -# +""" +Linear search works in any array. +T(n): O(n) +""" def linear_search(array, query): - for i in range(len(array)): - if array[i] == query: + """ + Find the index of the given element in the array. + There are no restrictions on the order of the elements in the array. + If the element couldn't be found, returns -1. + """ + for i, value in enumerate(array): + if value == query: return i - return -1 diff --git a/algorithms/search/next_greatest_letter.py b/algorithms/search/next_greatest_letter.py index 5abe001cd..26ec8536d 100644 --- a/algorithms/search/next_greatest_letter.py +++ b/algorithms/search/next_greatest_letter.py @@ -26,17 +26,17 @@ import bisect -""" -Using bisect libarary -""" def next_greatest_letter(letters, target): + """ + Using bisect libarary + """ index = bisect.bisect(letters, target) return letters[index % len(letters)] -""" -Using binary search: complexity O(logN) -""" def next_greatest_letter_v1(letters, target): + """ + Using binary search: complexity O(logN) + """ if letters[0] > target: return letters[0] if letters[len(letters) - 1] <= target: @@ -48,12 +48,12 @@ def next_greatest_letter_v1(letters, target): right = mid - 1 else: left = mid + 1 - return letters[left] + return letters[left] -""" -Brute force: complexity O(N) -""" def next_greatest_letter_v2(letters, target): + """ + Brute force: complexity O(N) + """ for index in letters: if index > target: return index diff --git a/algorithms/search/search_insert.py b/algorithms/search/search_insert.py index b10eb7d5f..279e64e40 100644 --- a/algorithms/search/search_insert.py +++ b/algorithms/search/search_insert.py @@ -1,14 +1,18 @@ """ -Given a sorted array and a target value, return the index if the target is -found. If not, return the index where it would be if it were inserted in order. - -For example: -[1,3,5,6], 5 -> 2 -[1,3,5,6], 2 -> 1 -[1,3,5,6], 7 -> 4 -[1,3,5,6], 0 -> 0 +Helper methods for implementing insertion sort. """ + def search_insert(array, val): + """ + Given a sorted array and a target value, return the index if the target is + found. If not, return the index where it would be if it were inserted in order. + + For example: + [1,3,5,6], 5 -> 2 + [1,3,5,6], 2 -> 1 + [1,3,5,6], 7 -> 4 + [1,3,5,6], 0 -> 0 + """ low = 0 high = len(array) - 1 while low <= high: diff --git a/algorithms/search/search_rotate.py b/algorithms/search/search_rotate.py index a92a15ee3..fe5474538 100644 --- a/algorithms/search/search_rotate.py +++ b/algorithms/search/search_rotate.py @@ -37,6 +37,10 @@ Recursion helps you understand better the above algorithm explanation """ def search_rotate(array, val): + """ + Finds the index of the given value in an array that has been sorted in + ascending order and then rotated at some unknown pivot. + """ low, high = 0, len(array) - 1 while low <= high: mid = (low + high) // 2 @@ -58,6 +62,10 @@ def search_rotate(array, val): # Recursion technique def search_rotate_recur(array, low, high, val): + """ + Finds the index of the given value in an array that has been sorted in + ascending order and then rotated at some unknown pivot. + """ if low >= high: return -1 mid = (low + high) // 2 @@ -66,10 +74,7 @@ def search_rotate_recur(array, low, high, val): if array[low] <= array[mid]: if array[low] <= val <= array[mid]: return search_rotate_recur(array, low, mid - 1, val) # Search left - else: - return search_rotate_recur(array, mid + 1, high, val) # Search right - else: - if array[mid] <= val <= array[high]: - return search_rotate_recur(array, mid + 1, high, val) # Search right - else: - return search_rotate_recur(array, low, mid - 1, val) # Search left + return search_rotate_recur(array, mid + 1, high, val) # Search right + if array[mid] <= val <= array[high]: + return search_rotate_recur(array, mid + 1, high, val) # Search right + return search_rotate_recur(array, low, mid - 1, val) # Search left diff --git a/algorithms/search/ternary_search.py b/algorithms/search/ternary_search.py index b7d36fb21..0d0ee1b66 100644 --- a/algorithms/search/ternary_search.py +++ b/algorithms/search/ternary_search.py @@ -1,37 +1,42 @@ """ -Ternary search is a divide and conquer algorithm that can be used to find an element in an array. -It is similar to binary search where we divide the array into two parts but in this algorithm, -we divide the given array into three parts and determine which has the key (searched element). +Ternary search is a divide and conquer algorithm that can be used to find an element in an array. +It is similar to binary search where we divide the array into two parts but in this algorithm, +we divide the given array into three parts and determine which has the key (searched element). We can divide the array into three parts by taking mid1 and mid2. Initially, l and r will be equal to 0 and n-1 respectively, where n is the length of the array. -mid1 = l + (r-l)/3 -mid2 = r – (r-l)/3 +mid1 = l + (r-l)/3 +mid2 = r – (r-l)/3 Note: Array needs to be sorted to perform ternary search on it. T(N) = O(log3(N)) log3 = log base 3 """ -def ternary_search(l, r, key, arr): - while r >= l: - - mid1 = l + (r-l) // 3 - mid2 = r - (r-l) // 3 +def ternary_search(left, right, key, arr): + """ + Find the given value (key) in an array sorted in ascending order. + Returns the index of the value if found, and -1 otherwise. + If the index is not in the range left..right (ie. left <= index < right) returns -1. + """ - if key == arr[mid1]: - return mid1 - if key == mid2: - return mid2 + while right >= left: + mid1 = left + (right-left) // 3 + mid2 = right - (right-left) // 3 - if key < arr[mid1]: + if key == arr[mid1]: + return mid1 + if key == mid2: + return mid2 + + if key < arr[mid1]: # key lies between l and mid1 - r = mid1 - 1 - elif key > arr[mid2]: + right = mid1 - 1 + elif key > arr[mid2]: # key lies between mid2 and r - l = mid2 + 1 - else: + left = mid2 + 1 + else: # key lies between mid1 and mid2 - l = mid1 + 1 - r = mid2 - 1 + left = mid1 + 1 + right = mid2 - 1 - # key not found - return -1 + # key not found + return -1 diff --git a/algorithms/search/two_sum.py b/algorithms/search/two_sum.py index 4251e5378..e8400fd1c 100644 --- a/algorithms/search/two_sum.py +++ b/algorithms/search/two_sum.py @@ -15,37 +15,57 @@ two_sum1: using dictionary as a hash table two_sum2: using two pointers """ -# Using binary search technique + def two_sum(numbers, target): - for i in range(len(numbers)): - second_val = target - numbers[i] + """ + Given a list of numbers sorted in ascending order, find the indices of two + numbers such that their sum is the given target. + + Using binary search. + """ + for i, number in enumerate(numbers): + second_val = target - number low, high = i+1, len(numbers)-1 while low <= high: mid = low + (high - low) // 2 if second_val == numbers[mid]: return [i + 1, mid + 1] - elif second_val > numbers[mid]: + + if second_val > numbers[mid]: low = mid + 1 else: high = mid - 1 + return None -# Using dictionary as a hash table def two_sum1(numbers, target): + """ + Given a list of numbers, find the indices of two numbers such that their + sum is the given target. + + Using a hash table. + """ dic = {} for i, num in enumerate(numbers): if target - num in dic: return [dic[target - num] + 1, i + 1] dic[num] = i + return None -# Using two pointers def two_sum2(numbers, target): - p1 = 0 # pointer 1 holds from left of array numbers - p2 = len(numbers) - 1 # pointer 2 holds from right of array numbers - while p1 < p2: - s = numbers[p1] + numbers[p2] - if s == target: - return [p1 + 1, p2 + 1] - elif s > target: - p2 = p2 - 1 + """ + Given a list of numbers sorted in ascending order, find the indices of two + numbers such that their sum is the given target. + + Using a bidirectional linear search. + """ + left = 0 # pointer 1 holds from left of array numbers + right = len(numbers) - 1 # pointer 2 holds from right of array numbers + while left < right: + current_sum = numbers[left] + numbers[right] + if current_sum == target: + return [left + 1, right + 1] + + if current_sum > target: + right = right - 1 else: - p1 = p1 + 1 + left = left + 1 diff --git a/algorithms/streaming/misra_gries.py b/algorithms/streaming/misra_gries.py index 31339094d..58fd84b5e 100644 --- a/algorithms/streaming/misra_gries.py +++ b/algorithms/streaming/misra_gries.py @@ -1,11 +1,17 @@ """ Implementation of the Misra-Gries algorithm. -Given a list of items and a value k, it returns the every item in the list that appears at least n/k times, where n is the length of the array -By default, k is set to 2, solving the majority problem. -For the majority problem, this algorithm only guarantees that if there is an element that appears more than n/2 times, it will be outputed. If there -is no such element, any arbitrary element is returned by the algorithm. Therefore, we need to iterate through again at the end. But since we have filtred -out the suspects, the memory complexity is significantly lower than it would be to create counter for every element in the list. +Given a list of items and a value k, it returns the every item in the list +that appears at least n/k times, where n is the length of the array + +By default, k is set to 2, solving the majority problem. + +For the majority problem, this algorithm only guarantees that if there is +an element that appears more than n/2 times, it will be outputed. If there +is no such element, any arbitrary element is returned by the algorithm. +Therefore, we need to iterate through again at the end. But since we have filtred +out the suspects, the memory complexity is significantly lower than +it would be to create counter for every element in the list. For example: Input misras_gries([1,4,4,4,5,4,4]) @@ -17,33 +23,38 @@ Input misras_gries([0,0,0,1,1,1] Output None """ + def misras_gries(array,k=2): - keys = {} - for i in range(len(array)): - val = str(array[i]) - if val in keys: - keys[val] = keys[val] + 1 - - elif len(keys) < k - 1: - keys[val] = 1 - - else: - for key in list(keys): - keys[key] = keys[key] - 1 - if keys[key] == 0: - del keys[key] - - suspects = keys.keys() - frequencies = {} - for suspect in suspects: - freq = _count_frequency(array,int(suspect)) - if freq >= len(array) / k: - frequencies[suspect] = freq - - return frequencies if len(frequencies) > 0 else None - + """Misra-Gries algorithm -def _count_frequency(array,element): - return array.count(element) + Keyword arguments: + array -- list of integers + k -- value of k (default 2) + """ + keys = {} + for i in array: + val = str(i) + if val in keys: + keys[val] = keys[val] + 1 + + elif len(keys) < k - 1: + keys[val] = 1 - + else: + for key in list(keys): + keys[key] = keys[key] - 1 + if keys[key] == 0: + del keys[key] + + suspects = keys.keys() + frequencies = {} + for suspect in suspects: + freq = _count_frequency(array,int(suspect)) + if freq >= len(array) / k: + frequencies[suspect] = freq + + return frequencies if len(frequencies) > 0 else None + + +def _count_frequency(array,element): + return array.count(element) diff --git a/algorithms/streaming/one_sparse_recovery.py b/algorithms/streaming/one_sparse_recovery.py index 084a9f7b8..18a26415e 100644 --- a/algorithms/streaming/one_sparse_recovery.py +++ b/algorithms/streaming/one_sparse_recovery.py @@ -1,61 +1,68 @@ -""" Non-negative 1-sparse recovery problem. This algorithm assumes we have a non negative dynamic stream. -Given a stream of tuples, where each tuple contains a number and a sign (+/-), it check if the stream is 1-sparse, meaning if the elements -in the stream cancel eacheother out in such a way that ther is only a unique number at the end. +""" +Non-negative 1-sparse recovery problem. +This algorithm assumes we have a non negative dynamic stream. + +Given a stream of tuples, where each tuple contains a number and a sign (+/-), it check if the +stream is 1-sparse, meaning if the elements in the stream cancel eacheother out in such +a way that ther is only a unique number at the end. Examples: -#1 +#1 Input: [(4,'+'), (2,'+'),(2,'-'),(4,'+'),(3,'+'),(3,'-')], -Output: 4 +Output: 4 Comment: Since 2 and 3 gets removed. -#2 +#2 Input: [(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'+')] -Output: 2 +Output: 2 Comment: No other numbers present #3 Input: [(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'+'),(2,'+'),(1,'+')] -Output: None +Output: None Comment: Not 1-sparse """ def one_sparse(array): - sum_signs = 0 - bitsum = [0]*32 - sum_values = 0 - for val,sign in array: - if sign == "+": - sum_signs += 1 - sum_values += val + """1-sparse algorithm + + Keyword arguments: + array -- stream of tuples + """ + sum_signs = 0 + bitsum = [0]*32 + sum_values = 0 + for val,sign in array: + if sign == "+": + sum_signs += 1 + sum_values += val + else: + sum_signs -= 1 + sum_values -= val + + _get_bit_sum(bitsum,val,sign) + + if sum_signs > 0 and _check_every_number_in_bitsum(bitsum,sum_signs): + return int(sum_values/sum_signs) else: - sum_signs -= 1 - sum_values -= val - - _get_bit_sum(bitsum,val,sign) - - if sum_signs > 0 and _check_every_number_in_bitsum(bitsum,sum_signs): - return int(sum_values/sum_signs) - else: - return None - + return None + #Helper function to check that every entry in the list is either 0 or the same as the #sum of signs def _check_every_number_in_bitsum(bitsum,sum_signs): - for val in bitsum: - if val != 0 and val != sum_signs : - return False - return True + for val in bitsum: + if val != 0 and val != sum_signs : + return False + return True # Adds bit representation value to bitsum array def _get_bit_sum(bitsum,val,sign): - i = 0 - if sign == "+": - while(val): - bitsum[i] += val & 1 - i +=1 - val >>=1 - else : - while(val): - bitsum[i] -= val & 1 - i +=1 - val >>=1 - - + i = 0 + if sign == "+": + while val: + bitsum[i] += val & 1 + i +=1 + val >>=1 + else : + while val: + bitsum[i] -= val & 1 + i +=1 + val >>=1 diff --git a/algorithms/tree/avl/avl.py b/algorithms/tree/avl/avl.py index 102fec82d..e3cbecb7f 100644 --- a/algorithms/tree/avl/avl.py +++ b/algorithms/tree/avl/avl.py @@ -1,3 +1,4 @@ +""" Imports TreeNodes""" from tree.tree import TreeNode @@ -17,9 +18,9 @@ def insert(self, key): Insert new key into node """ # Create new node - n = TreeNode(key) + node = TreeNode(key) if not self.node: - self.node = n + self.node = node self.node.left = AvlTree() self.node.right = AvlTree() elif key < self.node.val: @@ -65,7 +66,8 @@ def update_heights(self, recursive=True): if self.node.right: self.node.right.update_heights() - self.height = 1 + max(self.node.left.height, self.node.right.height) + self.height = 1 + max(self.node.left.height, + self.node.right.height) else: self.height = -1 diff --git a/algorithms/tree/b_tree.py b/algorithms/tree/b_tree.py index bedf31b6a..1e68b432f 100644 --- a/algorithms/tree/b_tree.py +++ b/algorithms/tree/b_tree.py @@ -3,7 +3,8 @@ at least t-1 keys (t children) and at most 2*t - 1 keys (2*t children) where t is the degree of b-tree. It is not a kind of typical bst tree, because this tree grows up. -B-tree is balanced which means that the difference between height of left subtree and right subtree is at most 1. +B-tree is balanced which means that the difference between height of left +subtree and right subtree is at most 1. Complexity n - number of elements @@ -18,6 +19,8 @@ class Node: + """ Class of Node""" + def __init__(self): # self.is_leaf = is_leaf self.keys = [] @@ -28,13 +31,16 @@ def __repr__(self): @property def is_leaf(self): + """ Return if it is a leaf""" return len(self.children) == 0 class BTree: - def __init__(self, t=2): - self.min_numbers_of_keys = t - 1 - self.max_number_of_keys = 2 * t - 1 + """ Class of BTree """ + + def __init__(self, t_val=2): + self.min_numbers_of_keys = t_val - 1 + self.max_number_of_keys = 2 * t_val - 1 self.root = Node() @@ -55,7 +61,8 @@ def _split_child(self, parent: Node, child_index: int): parent.children.insert(child_index + 1, new_right_child) def insert_key(self, key): - if len(self.root.keys) >= self.max_number_of_keys: # overflow, tree increases in height + """ overflow, tree increases in height """ + if len(self.root.keys) >= self.max_number_of_keys: new_root = Node() new_root.children.append(self.root) self.root = new_root @@ -72,26 +79,27 @@ def _insert_to_nonfull_node(self, node: Node, key): if node.is_leaf: node.keys.insert(i + 1, key) else: - if len(node.children[i + 1].keys) >= self.max_number_of_keys: # overflow + # overflow + if len(node.children[i + 1].keys) >= self.max_number_of_keys: self._split_child(node, i + 1) - if node.keys[i + 1] < key: # decide which child is going to have a new key + # decide which child is going to have a new key + if node.keys[i + 1] < key: i += 1 self._insert_to_nonfull_node(node.children[i + 1], key) def find(self, key) -> bool: + """ Finds key """ current_node = self.root while True: i = len(current_node.keys) - 1 while i >= 0 and current_node.keys[i] > key: i -= 1 - if i >= 0 and current_node.keys[i] == key: return True - elif current_node.is_leaf: + if current_node.is_leaf: return False - else: - current_node = current_node.children[i + 1] + current_node = current_node.children[i + 1] def remove_key(self, key): self._remove_key(self.root, key) @@ -101,10 +109,8 @@ def _remove_key(self, node: Node, key) -> bool: key_index = node.keys.index(key) if node.is_leaf: node.keys.remove(key) - return True else: self._remove_from_nonleaf_node(node, key_index) - return True except ValueError: # key not found in node @@ -114,7 +120,8 @@ def _remove_key(self, node: Node, key) -> bool: else: i = 0 number_of_keys = len(node.keys) - while i < number_of_keys and key > node.keys[i]: # decide in which subtree may be key + # decide in which subtree may be key + while i < number_of_keys and key > node.keys[i]: i += 1 action_performed = self._repair_tree(node, i) @@ -125,15 +132,16 @@ def _remove_key(self, node: Node, key) -> bool: def _repair_tree(self, node: Node, child_index: int) -> bool: child = node.children[child_index] - if self.min_numbers_of_keys < len(child.keys) <= self.max_number_of_keys: # The leaf/node is correct + # The leaf/node is correct + if self.min_numbers_of_keys < len(child.keys) <= self.max_number_of_keys: return False if child_index > 0 and len(node.children[child_index - 1].keys) > self.min_numbers_of_keys: self._rotate_right(node, child_index) return True - if (child_index < len(node.children) - 1 and - len(node.children[child_index + 1].keys) > self.min_numbers_of_keys): # 0 <-- 1 + if (child_index < len(node.children) - 1 + and len(node.children[child_index + 1].keys) > self.min_numbers_of_keys): # 0 <-- 1 self._rotate_left(node, child_index) return True @@ -156,8 +164,10 @@ def _rotate_left(self, parent_node: Node, child_index: int): parent_node.keys[child_index] = new_parent_key if not parent_node.children[child_index + 1].is_leaf: - ownerless_child = parent_node.children[child_index + 1].children.pop(0) - # make ownerless_child as a new biggest child (with highest key) -> transfer from right subtree to left subtree + ownerless_child = parent_node.children[child_index + + 1].children.pop(0) + # make ownerless_child as a new biggest child (with highest key) + # -> transfer from right subtree to left subtree parent_node.children[child_index].children.append(ownerless_child) def _rotate_right(self, parent_node: Node, child_index: int): @@ -170,9 +180,12 @@ def _rotate_right(self, parent_node: Node, child_index: int): parent_node.keys[child_index - 1] = new_parent_key if not parent_node.children[child_index - 1].is_leaf: - ownerless_child = parent_node.children[child_index - 1].children.pop() - # make ownerless_child as a new lowest child (with lowest key) -> transfer from left subtree to right subtree - parent_node.children[child_index].children.insert(0, ownerless_child) + ownerless_child = parent_node.children[child_index + - 1].children.pop() + # make ownerless_child as a new lowest child (with lowest key) + # -> transfer from left subtree to right subtree + parent_node.children[child_index].children.insert( + 0, ownerless_child) def _merge(self, parent_node: Node, to_merge_index: int, transfered_child_index: int): from_merge_node = parent_node.children.pop(transfered_child_index) @@ -191,9 +204,11 @@ def _remove_from_nonleaf_node(self, node: Node, key_index: int): key = node.keys[key_index] left_subtree = node.children[key_index] if len(left_subtree.keys) > self.min_numbers_of_keys: - largest_key = self._find_largest_and_delete_in_left_subtree(left_subtree) + largest_key = self._find_largest_and_delete_in_left_subtree( + left_subtree) elif len(node.children[key_index + 1].keys) > self.min_numbers_of_keys: - largest_key = self._find_largest_and_delete_in_right_subtree(node.children[key_index + 1]) + largest_key = self._find_largest_and_delete_in_right_subtree( + node.children[key_index + 1]) else: self._merge(node, key_index, key_index + 1) return self._remove_key(node, key) @@ -217,7 +232,8 @@ def _find_largest_and_delete_in_right_subtree(self, node: Node): else: ch_index = 0 self._repair_tree(node, ch_index) - largest_key_in_subtree = self._find_largest_and_delete_in_right_subtree(node.children[0]) + largest_key_in_subtree = self._find_largest_and_delete_in_right_subtree( + node.children[0]) # self._repair_tree(node, ch_index) return largest_key_in_subtree diff --git a/algorithms/tree/same_tree.py b/algorithms/tree/same_tree.py index 7e87e7e08..c2805a87c 100644 --- a/algorithms/tree/same_tree.py +++ b/algorithms/tree/same_tree.py @@ -7,11 +7,11 @@ """ -def is_same_tree(p, q): - if p is None and q is None: +def is_same_tree(tree_p, tree_q): + if tree_p is None and tree_q is None: return True - if p is not None and q is not None and p.val == q.val: - return is_same_tree(p.left, q.left) and is_same_tree(p.right, q.right) + if tree_p is not None and tree_q is not None and tree_p.val == tree_q.val: + return is_same_tree(tree_p.left, tree_q.left) and is_same_tree(tree_p.right, tree_q.right) return False # Time Complexity O(min(N,M)) diff --git a/algorithms/tree/traversal/inorder.py b/algorithms/tree/traversal/inorder.py index 17cadaf95..ff1798e08 100644 --- a/algorithms/tree/traversal/inorder.py +++ b/algorithms/tree/traversal/inorder.py @@ -2,6 +2,7 @@ Time complexity : O(n) ''' + class Node: def __init__(self, val, left=None, right=None): @@ -11,6 +12,7 @@ def __init__(self, val, left=None, right=None): def inorder(root): + """ In order function """ res = [] if not root: return res @@ -24,17 +26,18 @@ def inorder(root): root = root.right return res -# Recursive Implementation def inorder_rec(root, res=None): + """ Recursive Implementation """ if root is None: return [] - if res is None: + if res is None: res = [] inorder_rec(root.left, res) res.append(root.val) inorder_rec(root.right, res) return res + if __name__ == '__main__': n1 = Node(100) n2 = Node(50) @@ -46,6 +49,6 @@ def inorder_rec(root, res=None): n1.left, n1.right = n2, n3 n2.left, n2.right = n4, n5 n3.left, n3.right = n6, n7 - - assert inorder(n1) == [25, 50, 75, 100, 125, 150, 175] + + assert inorder(n1) == [25, 50, 75, 100, 125, 150, 175] assert inorder_rec(n1) == [25, 50, 75, 100, 125, 150, 175] diff --git a/algorithms/tree/traversal/preorder.py b/algorithms/tree/traversal/preorder.py index 45346ba87..c290371b7 100644 --- a/algorithms/tree/traversal/preorder.py +++ b/algorithms/tree/traversal/preorder.py @@ -2,7 +2,9 @@ Time complexity : O(n) ''' + class Node: + """ This is a class of Node """ def __init__(self, val, left=None, right=None): self.val = val @@ -11,6 +13,7 @@ def __init__(self, val, left=None, right=None): def preorder(root): + """ Function to Preorder """ res = [] if not root: return res @@ -25,8 +28,8 @@ def preorder(root): stack.append(root.left) return res -# Recursive Implementation def preorder_rec(root, res=None): + """ Recursive Implementation """ if root is None: return [] if res is None: @@ -35,4 +38,3 @@ def preorder_rec(root, res=None): preorder_rec(root.left, res) preorder_rec(root.right, res) return res - diff --git a/algorithms/unionfind/count_islands.py b/algorithms/unionfind/count_islands.py index 56bed5bdd..ca94aa0ad 100644 --- a/algorithms/unionfind/count_islands.py +++ b/algorithms/unionfind/count_islands.py @@ -1,78 +1,126 @@ """ -A 2d grid map of m rows and n columns is initially filled with water. -We may perform an addLand operation which turns the water at position -(row, col) into a land. Given a list of positions to operate, -count the number of islands after each addLand operation. -An island is surrounded by water and is formed by connecting adjacent -lands horizontally or vertically. -You may assume all four edges of the grid are all surrounded by water. - -Given m = 3, n = 3, positions = [[0,0], [0,1], [1,2], [2,1]]. -Initially, the 2d grid grid is filled with water. -(Assume 0 represents water and 1 represents land). - -0 0 0 -0 0 0 -0 0 0 -Operation #1: addLand(0, 0) turns the water at grid[0][0] into a land. - -1 0 0 -0 0 0 Number of islands = 1 -0 0 0 -Operation #2: addLand(0, 1) turns the water at grid[0][1] into a land. - -1 1 0 -0 0 0 Number of islands = 1 -0 0 0 -Operation #3: addLand(1, 2) turns the water at grid[1][2] into a land. - -1 1 0 -0 0 1 Number of islands = 2 -0 0 0 -Operation #4: addLand(2, 1) turns the water at grid[2][1] into a land. - -1 1 0 -0 0 1 Number of islands = 3 -0 1 0 +Defines the Union-Find (or Disjoint Set) data structure. + +A disjoint set is made up of a number of elements contained within another +number of sets. Initially, elements are put in their own set, but sets may be +merged using the `unite` operation. We can check if two elements are in the +same seet by comparing their `root`s. If they are identical, the two elements +are in the same set. All operations can be completed in O(a(n)) where `n` is +the number of elements, and `a` the inverse ackermann function. a(n) grows so +slowly that it might as well be constant for any conceivable `n`. """ +class Union: + """ + A Union-Find data structure. + + Consider the following sequence of events: + Starting with the elements 1, 2, 3, and 4: + + {1} {2} {3} {4} + + Initally they all live in their own sets, which means that `root(1) != + root(3)`, however, if we call `unite(1, 3)` we would then have the following: + + {1,3} {2} {4} + + Now we have `root(1) == root(3)`, but it is still the case that `root(1) != root(2)`. + + We may call `unite(2, 4)` and end up with: + + {1,3} {2,4} + + Again we have `root(1) != root(2)`. But after `unite(3, 4)` we end up with: + + {1,2,3,4} + + which results in `root(1) == root(2)`. + """ -class Solution(object): - def num_islands2(self, m, n, positions): - ans = [] - islands = Union() - for p in map(tuple, positions): - islands.add(p) - for dp in (0, 1), (0, -1), (1, 0), (-1, 0): - q = (p[0] + dp[0], p[1] + dp[1]) - if q in islands.id: - islands.unite(p, q) - ans += [islands.count] - return ans - -class Union(object): def __init__(self): - self.id = {} - self.sz = {} + self.parents = {} + self.size = {} self.count = 0 - def add(self, p): - self.id[p] = p - self.sz[p] = 1 + def add(self, element): + """ + Add a new set containing the single element + """ + + self.parents[element] = element + self.size[element] = 1 self.count += 1 - def root(self, i): - while i != self.id[i]: - self.id[i] = self.id[self.id[i]] - i = self.id[i] - return i + def root(self, element): + """ + Find the root element which represents the set of a given element. + That is, all elements that are in the same set will return the same + root element. + """ + + while element != self.parents[element]: + self.parents[element] = self.parents[self.parents[element]] + element = self.parents[element] + return element + + def unite(self, element1, element2): + """ + Finds the sets which contains the two elements and merges them into a + single set. + """ - def unite(self, p, q): - i, j = self.root(p), self.root(q) - if i == j: + root1, root2 = self.root(element1), self.root(element2) + if root1 == root2: return - if self.sz[i] > self.sz[j]: - i, j = j, i - self.id[i] = j - self.sz[j] += self.sz[i] + if self.size[root1] > self.size[root2]: + root1, root2 = root2, root1 + self.parents[root1] = root2 + self.size[root2] += self.size[root1] self.count -= 1 + +def num_islands(positions): + """ + Given a list of positions to operate, count the number of islands + after each addLand operation. An island is surrounded by water and is + formed by connecting adjacent lands horizontally or vertically. You may + assume all four edges of the grid are all surrounded by water. + + Given a 3x3 grid, positions = [[0,0], [0,1], [1,2], [2,1]]. + Initially, the 2d grid grid is filled with water. + (Assume 0 represents water and 1 represents land). + + 0 0 0 + 0 0 0 + 0 0 0 + Operation #1: addLand(0, 0) turns the water at grid[0][0] into a land. + + 1 0 0 + 0 0 0 Number of islands = 1 + 0 0 0 + Operation #2: addLand(0, 1) turns the water at grid[0][1] into a land. + + 1 1 0 + 0 0 0 Number of islands = 1 + 0 0 0 + Operation #3: addLand(1, 2) turns the water at grid[1][2] into a land. + + 1 1 0 + 0 0 1 Number of islands = 2 + 0 0 0 + Operation #4: addLand(2, 1) turns the water at grid[2][1] into a land. + + 1 1 0 + 0 0 1 Number of islands = 3 + 0 1 0 + """ + + ans = [] + islands = Union() + for position in map(tuple, positions): + islands.add(position) + for delta in (0, 1), (0, -1), (1, 0), (-1, 0): + adjacent = (position[0] + delta[0], position[1] + delta[1]) + if adjacent in islands.parents: + islands.unite(position, adjacent) + ans += [islands.count] + return ans diff --git a/tests/test_dp.py b/tests/test_dp.py index e8dca0bce..e92800a11 100644 --- a/tests/test_dp.py +++ b/tests/test_dp.py @@ -11,8 +11,10 @@ Job, schedule, Item, get_maximum_value, longest_increasing_subsequence, - int_divide, find_k_factor, - planting_trees + longest_increasing_subsequence_optimized, + longest_increasing_subsequence_optimized2, + int_divide,find_k_factor, + planting_trees, regex_matching ) @@ -208,6 +210,53 @@ def test_simple2(self): # assert self.assertEqual(res, 9.28538328578604) + +class TestRegexMatching(unittest.TestCase): + def test_none_0(self): + s = "" + p = "" + self.assertTrue(regex_matching.is_match(s, p)) + + def test_none_1(self): + s = "" + p = "a" + self.assertFalse(regex_matching.is_match(s, p)) + + def test_no_symbol_equal(self): + s = "abcd" + p = "abcd" + self.assertTrue(regex_matching.is_match(s, p)) + + def test_no_symbol_not_equal_0(self): + s = "abcd" + p = "efgh" + self.assertFalse(regex_matching.is_match(s, p)) + + def test_no_symbol_not_equal_1(self): + s = "ab" + p = "abb" + self.assertFalse(regex_matching.is_match(s, p)) + + def test_symbol_0(self): + s = "" + p = "a*" + self.assertTrue(regex_matching.is_match(s, p)) + + def test_symbol_1(self): + s = "a" + p = "ab*" + self.assertTrue(regex_matching.is_match(s, p)) + + def test_symbol_2(self): + # E.g. + # s a b b + # p 1 0 0 0 + # a 0 1 0 0 + # b 0 0 1 0 + # * 0 1 1 1 + s = "abb" + p = "ab*" + self.assertTrue(regex_matching.is_match(s, p)) if __name__ == '__main__': diff --git a/tests/test_graph.py b/tests/test_graph.py index 8caf858e7..eb50dd4e1 100644 --- a/tests/test_graph.py +++ b/tests/test_graph.py @@ -10,6 +10,10 @@ from algorithms.graph import bellman_ford from algorithms.graph import count_connected_number_of_component from algorithms.graph import prims_minimum_spanning +from algorithms.graph import check_digraph_strongly_connected +from algorithms.graph import cycle_detection +from algorithms.graph import find_path +from algorithms.graph import path_between_two_vertices_in_digraph import unittest @@ -278,3 +282,70 @@ def test_prim_spanning(self): 4: [[6, 1], [9, 2], [8, 3]] } self.assertEqual(19, prims_minimum_spanning(graph2)) + +class TestDigraphStronglyConnected(unittest.TestCase): + def test_digraph_strongly_connected(self): + g1 = check_digraph_strongly_connected.Graph(5) + g1.add_edge(0, 1) + g1.add_edge(1, 2) + g1.add_edge(2, 3) + g1.add_edge(3, 0) + g1.add_edge(2, 4) + g1.add_edge(4, 2) + self.assertTrue(g1.is_strongly_connected()) + + g2 = check_digraph_strongly_connected.Graph(4) + g2.add_edge(0, 1) + g2.add_edge(1, 2) + g2.add_edge(2, 3) + self.assertFalse(g2.is_strongly_connected()) + +class TestCycleDetection(unittest.TestCase): + def test_cycle_detection_with_cycle(self): + graph = {'A': ['B', 'C'], + 'B': ['D'], + 'C': ['F'], + 'D': ['E', 'F'], + 'E': ['B'], + 'F': []} + self.assertTrue(cycle_detection.contains_cycle(graph)) + + def test_cycle_detection_with_no_cycle(self): + graph = {'A': ['B', 'C'], + 'B': ['D', 'E'], + 'C': ['F'], + 'D': ['E'], + 'E': [], + 'F': []} + self.assertFalse(cycle_detection.contains_cycle(graph)) + +class TestFindPath(unittest.TestCase): + def test_find_all_paths(self): + graph = {'A': ['B', 'C'], + 'B': ['C', 'D'], + 'C': ['D', 'F'], + 'D': ['C'], + 'E': ['F'], + 'F': ['C']} + + paths = find_path.find_all_path(graph, 'A', 'F') + print(paths) + self.assertEqual(sorted(paths), sorted([ + ['A', 'C', 'F'], + ['A', 'B', 'C', 'F'], + ['A', 'B', 'D', 'C', 'F'], + ])) + +class TestPathBetweenTwoVertices(unittest.TestCase): + def test_node_is_reachable(self): + g = path_between_two_vertices_in_digraph.Graph(4) + 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) + + self.assertTrue(g.is_reachable(1, 3)) + self.assertFalse(g.is_reachable(3, 1)) + diff --git a/tests/test_heap.py b/tests/test_heap.py index 11e323e5f..afae0d93e 100644 --- a/tests/test_heap.py +++ b/tests/test_heap.py @@ -28,7 +28,7 @@ def test_insert(self): self.min_heap.insert(2) self.assertEqual([0, 2, 50, 4, 55, 90, 87, 7], self.min_heap.heap) - self.assertEqual(7, self.min_heap.currentSize) + self.assertEqual(7, self.min_heap.current_size) def test_remove_min(self): ret = self.min_heap.remove_min() @@ -38,7 +38,7 @@ def test_remove_min(self): self.assertEqual(4, ret) self.assertEqual([0, 7, 50, 87, 55, 90], self.min_heap.heap) - self.assertEqual(5, self.min_heap.currentSize) + self.assertEqual(5, self.min_heap.current_size) class TestSuite(unittest.TestCase): From 6440664a31822686b4fbf8282a7b34fa5d51118c Mon Sep 17 00:00:00 2001 From: abhishek singh <44000178+iabhishek15@users.noreply.github.com> Date: Tue, 8 Mar 2022 05:01:42 -0800 Subject: [PATCH 080/101] Added Kadane algorithm for max_contiguous_subsequence_sum problem with requested changes. (#861) * initial commit * initial commit! * Added Kadane's Algorithm for max_contiguous_subsequence_sum problem! * Update README.md * Update max_contiguous_subsequence_sum.py * Update max_contiguous_subsequence_sum.py * fix #854 --- README.md | 2 + algorithms/greedy/__init__.py | 1 + .../greedy/max_contiguous_subsequence_sum.py | 42 +++++++++++++++++++ tests/test_greedy.py | 21 ++++++++++ 4 files changed, 66 insertions(+) create mode 100644 algorithms/greedy/__init__.py create mode 100644 algorithms/greedy/max_contiguous_subsequence_sum.py create mode 100644 tests/test_greedy.py diff --git a/README.md b/README.md index 2234b67fa..0fce1591d 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,8 @@ If you want to uninstall algorithms, it is as simple as: - [two_sum](algorithms/arrays/two_sum.py) - [move_zeros](algorithms/arrays/move_zeros.py) - [n_sum](algorithms/arrays/n_sum.py) +- [greedy](algorithms/greedy/) + - [max_contiguous_subsequence_sum](algorithms/greedy/max_contiguous_subsequence_sum.py) - [automata](algorithms/automata) - [DFA](algorithms/automata/dfa.py) - [backtrack](algorithms/backtrack) diff --git a/algorithms/greedy/__init__.py b/algorithms/greedy/__init__.py new file mode 100644 index 000000000..66184782e --- /dev/null +++ b/algorithms/greedy/__init__.py @@ -0,0 +1 @@ +from .max_contiguous_subsequence_sum import * diff --git a/algorithms/greedy/max_contiguous_subsequence_sum.py b/algorithms/greedy/max_contiguous_subsequence_sum.py new file mode 100644 index 000000000..c5eae3d6e --- /dev/null +++ b/algorithms/greedy/max_contiguous_subsequence_sum.py @@ -0,0 +1,42 @@ +''' +Algorithm used => Kadane's Algorithm + +kadane's algorithm is used for finding the maximum sum of contiguous subsequence in a sequence. +It is considered a greedy/dp algorithm but I think they more greedy than dp +here are some of the examples to understand the use case more clearly +Example1 => [-2, 3, 8, -1, 4] +result => {3, 8, -1, 4} => 14 +Example2 => [-1, 1, 0] +result => {1} => 1 +Example3 => [-1, -3, -4] +result => -1 +Example1 => [-2, 3, 8, -12, 8, 4] +result => {8, 4} => 12 +Basic Algorithm Idea + If the sum of the current contiguous subsequence after adding the value at the current position is less than the value + at the current position then we know that it will be better if we start the current contiguous subsequence from this position. + Else we add the value at the current position to the current contiguous subsequence. +Note + In the implementation, the contiguous subsequence has at least one element. + If it can have 0 elements then the result will be max(max_till_now, 0) +''' + + +def max_contiguous_subsequence_sum(arr) -> int: + arr_size = len(arr) + + if arr_size == 0: + return 0 + + max_till_now = arr[0] + curr_sub_sum = 0 + + for i in range(0, arr_size): + if curr_sub_sum + arr[i] < arr[i]: + curr_sub_sum = arr[i] + else: + curr_sub_sum += arr[i] + + max_till_now = max(max_till_now, curr_sub_sum) + + return max_till_now diff --git a/tests/test_greedy.py b/tests/test_greedy.py new file mode 100644 index 000000000..095f2a282 --- /dev/null +++ b/tests/test_greedy.py @@ -0,0 +1,21 @@ +from algorithms.greedy import ( + max_contiguous_subsequence_sum, +) + +import unittest + +class TestMaxContiguousSubsequenceSum(unittest.TestCase): + def test_max_contiguous_subsequence_sum(self): + arr1 = [-2, 3, 8, -1, 4] + arr2 = [-1, 1, 0] + arr3 = [-1, -3, -4] + arr4 = [-2, 3, 8, -12, 8, 4] + + self.assertEqual(max_contiguous_subsequence_sum(arr1), 14) + self.assertEqual(max_contiguous_subsequence_sum(arr2), 1) + self.assertEqual(max_contiguous_subsequence_sum(arr3), -1) + self.assertEqual(max_contiguous_subsequence_sum(arr4), 12) + +if __name__ == '__main__': + + unittest.main() \ No newline at end of file From 451614b6620e98ab58a83b0c7a2c79aa941ea4da Mon Sep 17 00:00:00 2001 From: Bogyeong Kim <82003678+boggy-cs@users.noreply.github.com> Date: Wed, 16 Mar 2022 18:43:09 +0900 Subject: [PATCH 081/101] added test case for test_limit (#819) --- tests/test_array.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_array.py b/tests/test_array.py index 83a4840d5..eaabfc710 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -385,6 +385,7 @@ def test_top_1(self): class TestLimit(unittest.TestCase): def test_limit(self): + self.assertListEqual(limit([1, 2, 3, 4, 5]), [1, 2, 3, 4, 5]) self.assertListEqual(limit([1, 2, 3, 4, 5], 2, 4), [2, 3, 4]) self.assertListEqual(limit([1, 2, 3, 4, 5], 2), [2, 3, 4, 5]) self.assertListEqual(limit([1, 2, 3, 4, 5], None, 4), [1, 2, 3, 4]) From 5f1a7863818b016a62ff03a45980f7ec8a970a07 Mon Sep 17 00:00:00 2001 From: Mark <38922368+mantaur@users.noreply.github.com> Date: Sun, 20 Mar 2022 12:10:40 +0100 Subject: [PATCH 082/101] Add num_perfect_squares #767 (#848) * Added num_perfect_squares * Added final test for increased coverage * Fix requested changes for issue #767. Also improved documentation and added 1 test case. * Doc update to clarify intent for issue #767 This documentation update clarifies the intent and order of each code piece. Co-authored-by: unknown Co-authored-by: ntomsic Co-authored-by: Keon --- README.md | 1 + algorithms/maths/__init__.py | 1 + algorithms/maths/num_perfect_squares.py | 47 +++++++++++++++++++++++++ tests/test_maths.py | 20 +++++++++++ 4 files changed, 69 insertions(+) create mode 100644 algorithms/maths/num_perfect_squares.py diff --git a/README.md b/README.md index 0fce1591d..0ec23733f 100644 --- a/README.md +++ b/README.md @@ -225,6 +225,7 @@ If you want to uninstall algorithms, it is as simple as: - [next_bigger](algorithms/maths/next_bigger.py) - [next_perfect_square](algorithms/maths/next_perfect_square.py) - [nth_digit](algorithms/maths/nth_digit.py) + - [num_perfect_squares](algorithms/maths/num_perfect_squares.py) - [polynomial](algorithms/maths/polynomial.py) - [power](algorithms/maths/power.py) - [prime_check](algorithms/maths/prime_check.py) diff --git a/algorithms/maths/__init__.py b/algorithms/maths/__init__.py index 8a3a9e627..d5499b094 100644 --- a/algorithms/maths/__init__.py +++ b/algorithms/maths/__init__.py @@ -25,3 +25,4 @@ from .power import * from .magic_number import * from .krishnamurthy_number import * +from .num_perfect_squares import * diff --git a/algorithms/maths/num_perfect_squares.py b/algorithms/maths/num_perfect_squares.py new file mode 100644 index 000000000..e02eeb768 --- /dev/null +++ b/algorithms/maths/num_perfect_squares.py @@ -0,0 +1,47 @@ +""" +Given an integer num_perfect_squares will return the minimum amount of perfect squares are required +to sum to the specified number. Lagrange's four-square theorem gives us that the answer will always +be between 1 and 4 (https://en.wikipedia.org/wiki/Lagrange%27s_four-square_theorem). + +Some examples: +Number | Perfect Squares representation | Answer +-------|--------------------------------|-------- +9 | 3^2 | 1 +10 | 3^2 + 1^2 | 2 +12 | 2^2 + 2^2 + 2^2 | 3 +31 | 5^2 + 2^2 + 1^2 + 1^2 | 4 +""" + +import math + +def num_perfect_squares(number): + """ + Returns the smallest number of perfect squares that sum to the specified number. + :return: int between 1 - 4 + """ + # If the number is a perfect square then we only need 1 number. + if int(math.sqrt(number))**2 == number: + return 1 + + # We check if https://en.wikipedia.org/wiki/Legendre%27s_three-square_theorem holds and divide + # the number accordingly. Ie. if the number can be written as a sum of 3 squares (where the + # 0^2 is allowed), which is possible for all numbers except those of the form: 4^a(8b + 7). + while number > 0 and number % 4 == 0: + number /= 4 + + # If the number is of the form: 4^a(8b + 7) it can't be expressed as a sum of three (or less + # excluding the 0^2) perfect squares. If the number was of that form, the previous while loop + # divided away the 4^a, so by now it would be of the form: 8b + 7. So check if this is the case + # and return 4 since it neccessarily must be a sum of 4 perfect squares, in accordance + # with https://en.wikipedia.org/wiki/Lagrange%27s_four-square_theorem. + if number % 8 == 7: + return 4 + + # By now we know that the number wasn't of the form 4^a(8b + 7) so it can be expressed as a sum + # of 3 or less perfect squares. Try first to express it as a sum of 2 perfect squares, and if + # that fails, we know finally that it can be expressed as a sum of 3 perfect squares. + for i in range(1, int(math.sqrt(number)) + 1): + if int(math.sqrt(number - i**2))**2 == number - i**2: + return 2 + + return 3 diff --git a/tests/test_maths.py b/tests/test_maths.py index ec8f65798..032145556 100644 --- a/tests/test_maths.py +++ b/tests/test_maths.py @@ -24,6 +24,7 @@ find_primitive_root, num_digits, diffie_hellman_key_exchange, krishnamurthy_number, + num_perfect_squares, chinese_remainder_theorem, ) @@ -502,6 +503,25 @@ def test_num_digits(self): self.assertEqual(3, num_digits(-254)) + +class TestNumberOfPerfectSquares(unittest.TestCase): + """[summary] + Test for the file num_perfect_squares.py + + Arguments: + unittest {[type]} -- [description] + """ + def test_num_perfect_squares(self): + self.assertEqual(4,num_perfect_squares(31)) + self.assertEqual(3,num_perfect_squares(12)) + self.assertEqual(2,num_perfect_squares(13)) + self.assertEqual(2,num_perfect_squares(10)) + self.assertEqual(4,num_perfect_squares(1500)) + self.assertEqual(2,num_perfect_squares(1548524521)) + self.assertEqual(3,num_perfect_squares(9999999993)) + self.assertEqual(1,num_perfect_squares(9)) + + class TestChineseRemainderSolver(unittest.TestCase): def test_k_three(self): # Example which should give the answer 143 From 7d70e39c012a9fc8b0a94813786f85626b093941 Mon Sep 17 00:00:00 2001 From: code-review-doctor <72647856+code-review-doctor@users.noreply.github.com> Date: Tue, 14 Jun 2022 17:06:43 +0100 Subject: [PATCH 083/101] Fix issue probably-meant-fstring found at https://codereview.doctor (#864) --- algorithms/maths/diffie_hellman_key_exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/algorithms/maths/diffie_hellman_key_exchange.py b/algorithms/maths/diffie_hellman_key_exchange.py index 0d8b751fd..b70b000af 100644 --- a/algorithms/maths/diffie_hellman_key_exchange.py +++ b/algorithms/maths/diffie_hellman_key_exchange.py @@ -179,6 +179,6 @@ def diffie_hellman_key_exchange(a, p, option = None): a_sh_k = alice_shared_key(b_pu_k, a_pr_k, p) b_sh_k = bob_shared_key(a_pu_k, b_pr_k, p) print (f"Shared key calculated by Alice = {a_sh_k}") - print ("Shared key calculated by Bob = {b_sh_k}") + print (f"Shared key calculated by Bob = {b_sh_k}") return a_sh_k == b_sh_k From 980d95388dab87c96ab1c774aa33d83a487db767 Mon Sep 17 00:00:00 2001 From: Brknyi <57301527+Brknyi@users.noreply.github.com> Date: Tue, 14 Jun 2022 18:10:06 +0200 Subject: [PATCH 084/101] Fix test_backtrack.py (#865) Fix invalid test related to t.sort() vs sorted(t) --- tests/test_backtrack.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_backtrack.py b/tests/test_backtrack.py index 4e9b9f612..516ed6d2d 100644 --- a/tests/test_backtrack.py +++ b/tests/test_backtrack.py @@ -184,8 +184,9 @@ def test_normal(self): ] words = ["oath", "pea", "eat", "rain"] - self.assertEqual(find_words(board, words).sort(), - ['oath', 'eat'].sort()) + result = find_words(board, words) + test_result = ['oath', 'eat'] + self.assertEqual(sorted(result),sorted(test_result)) def test_none(self): board = [ From 8aa29d7636cea5451a7a4f147a9f43a92aaa5b71 Mon Sep 17 00:00:00 2001 From: Sagnik Barman <98907729+darthdaenerys@users.noreply.github.com> Date: Wed, 6 Jul 2022 22:52:31 +0530 Subject: [PATCH 085/101] fix: removed extra blank line between sections (#870) --- algorithms/arrays/josephus.py | 1 - 1 file changed, 1 deletion(-) diff --git a/algorithms/arrays/josephus.py b/algorithms/arrays/josephus.py index 3d0ad2329..7805a388d 100644 --- a/algorithms/arrays/josephus.py +++ b/algorithms/arrays/josephus.py @@ -9,7 +9,6 @@ Output: 369485271 """ - def josephus(int_list, skip): skip = skip - 1 # list starts with 0 index idx = 0 From e63bc4d78042f5e469d966402b517214ef7ea70f Mon Sep 17 00:00:00 2001 From: Igor Izvekov Date: Wed, 27 Jul 2022 15:26:55 +0300 Subject: [PATCH 086/101] "delete_fixup" optimization (#873) * "delete_fixup" optimization * node_min.parent != node -> node_min.parent is not node --- algorithms/tree/red_black_tree/red_black_tree.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/algorithms/tree/red_black_tree/red_black_tree.py b/algorithms/tree/red_black_tree/red_black_tree.py index 4b3c0d66b..20b6e7bc3 100644 --- a/algorithms/tree/red_black_tree/red_black_tree.py +++ b/algorithms/tree/red_black_tree/red_black_tree.py @@ -194,7 +194,7 @@ def delete(self, node): node_color = node_min.color temp_node = node_min.right ## - if node_min.parent != node: + if node_min.parent is not node: self.transplant(node_min, node_min.right) node_min.right = node.right node_min.right.parent = node_min @@ -208,9 +208,9 @@ def delete(self, node): def delete_fixup(self, node): # 4 cases - while node != self.root and node.color == 0: + while node is not self.root and node.color == 0: # node is not root and color is black - if node == node.parent.left: + if node is node.parent.left: # node is left node node_brother = node.parent.right From fac67d6c828398171938754e810a36653c457bc5 Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Fri, 29 Jul 2022 20:37:57 +1000 Subject: [PATCH 087/101] docs: fix simple typo, occured -> occurred (#869) There is a small typo in algorithms/sort/heap_sort.py. Should read `occurred` rather than `occured`. --- algorithms/sort/heap_sort.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/algorithms/sort/heap_sort.py b/algorithms/sort/heap_sort.py index ec4c7fa2b..f07d4bf61 100644 --- a/algorithms/sort/heap_sort.py +++ b/algorithms/sort/heap_sort.py @@ -38,7 +38,7 @@ def max_heapify(arr, end, simulation, iteration): if simulation: iteration = iteration + 1 print("iteration",iteration,":",*arr) - # If no swap occured, no need to keep iterating + # If no swap occurred, no need to keep iterating else: break arr[0], arr[end] = arr[end], arr[0] @@ -86,7 +86,7 @@ def min_heapify(arr, start, simulation, iteration): if simulation: iteration = iteration + 1 print("iteration",iteration,":",*arr) - # If no swap occured, no need to keep iterating + # If no swap occurred, no need to keep iterating else: break return iteration From 52b9408c98eaa8458083b27410c0c364406d6e24 Mon Sep 17 00:00:00 2001 From: Abhay Patil <72353341+patilabhay679@users.noreply.github.com> Date: Sat, 12 Nov 2022 10:24:30 +0530 Subject: [PATCH 088/101] fixed typo. (#878) --- algorithms/strings/fizzbuzz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/algorithms/strings/fizzbuzz.py b/algorithms/strings/fizzbuzz.py index 09078563b..c811afa25 100644 --- a/algorithms/strings/fizzbuzz.py +++ b/algorithms/strings/fizzbuzz.py @@ -1,5 +1,5 @@ """ -Wtite a function that returns an array containing the numbers from 1 to N, +Write a function that returns an array containing the numbers from 1 to N, where N is the parametered value. N will never be less than 1. Replace certain values however if any of the following conditions are met: From 2a129ba80efc25fca6ac58411c8df2457655cdc2 Mon Sep 17 00:00:00 2001 From: joseph-alan-jose <124856003+joseph-alan-jose@users.noreply.github.com> Date: Wed, 8 Mar 2023 15:01:23 +0530 Subject: [PATCH 089/101] Fixed typos in two files (#898) Co-authored-by: rahulrameshan --- CONTRIBUTING.md | 2 +- algorithms/matrix/matrix_exponentiation.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3ccd4eb6c..6eac6c029 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ agree to abide by the [Code of Conduct](CODE_OF_CONDUCT.md). * After that create a branch for your changes. For example: * add_XXX if you will add new algorithms or data structures. - * fix_XXX if you will fixe a bug on a certain algorithm or data structure. + * fix_XXX if you will fix a bug on a certain algorithm or data structure. * test_XXX if you wrote a test/s. * doc_XXX if you added to or edited documentation. diff --git a/algorithms/matrix/matrix_exponentiation.py b/algorithms/matrix/matrix_exponentiation.py index 2c836fe07..d9b45ba21 100644 --- a/algorithms/matrix/matrix_exponentiation.py +++ b/algorithms/matrix/matrix_exponentiation.py @@ -1,6 +1,6 @@ def multiply(matA: list, matB: list) -> list: """ - Multiplies two square matrices matA and matB od size n x n + Multiplies two square matrices matA and matB of size n x n Time Complexity: O(n^3) """ n = len(matA) From fd86fd1e8a1ca585562382db59603ece58a31112 Mon Sep 17 00:00:00 2001 From: Abhay Patil <72353341+patilabhay679@users.noreply.github.com> Date: Wed, 8 Mar 2023 15:02:16 +0530 Subject: [PATCH 090/101] modified the return from both main and helper. (#886) It looks like there is no need to create a copy, we can replace the new elements inplace. --- algorithms/sort/merge_sort.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/algorithms/sort/merge_sort.py b/algorithms/sort/merge_sort.py index 351a4c5e8..e2d7f05b9 100644 --- a/algorithms/sort/merge_sort.py +++ b/algorithms/sort/merge_sort.py @@ -10,7 +10,9 @@ def merge_sort(arr): left, right = merge_sort(arr[:mid]), merge_sort(arr[mid:]) # Merge each side together - return merge(left, right, arr.copy()) + # return merge(left, right, arr.copy()) # changed, no need to copy, mutate inplace. + merge(left,right,arr) + return arr def merge(left, right, merged): @@ -35,4 +37,4 @@ def merge(left, right, merged): merged[left_cursor + right_cursor] = right[right_cursor] # Return result - return merged + # return merged # do not return anything, as it is replacing inplace. From e24247fb1455bc2d1ddfcd5d8923f9a35cd2a27a Mon Sep 17 00:00:00 2001 From: Mohan Date: Wed, 8 Mar 2023 15:02:38 +0530 Subject: [PATCH 091/101] Fix search_range and add test (#868) --- algorithms/search/search_range.py | 14 +++++++------- tests/test_search.py | 5 +++++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/algorithms/search/search_range.py b/algorithms/search/search_range.py index 116d8f541..f0f2bfba0 100644 --- a/algorithms/search/search_range.py +++ b/algorithms/search/search_range.py @@ -17,17 +17,17 @@ def search_range(nums, target): """ low = 0 high = len(nums) - 1 - while low <= high: + # breaks at low == high + # both pointing to first occurence of target + while low < high: mid = low + (high - low) // 2 - if target < nums[mid]: - high = mid - 1 - elif target > nums[mid]: - low = mid + 1 + if target <= nums[mid]: + high = mid else: - break + low = mid + 1 for j in range(len(nums) - 1, -1, -1): if nums[j] == target: - return [mid, j] + return [low, j] return [-1, -1] diff --git a/tests/test_search.py b/tests/test_search.py index 8f9555048..f515cfcb9 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -91,6 +91,11 @@ def test_search_range(self): self.assertEqual([1, 2], search_range(array, 7)) self.assertEqual([-1, -1], search_range(array, 11)) + array = [5, 7, 7, 7, 7, 8, 8, 8, 8, 10] + self.assertEqual([5, 8], search_range(array, 8)) + self.assertEqual([1, 4], search_range(array, 7)) + self.assertEqual([-1, -1], search_range(array, 11)) + def test_find_min_rotate(self): array = [4, 5, 6, 7, 0, 1, 2] self.assertEqual(0, find_min_rotate(array)) From 51f93c600bd2e20c33c46b014819bb1c5c0e429c Mon Sep 17 00:00:00 2001 From: ekorre1001 <78041058+ekorre1001@users.noreply.github.com> Date: Wed, 8 Mar 2023 10:33:02 +0100 Subject: [PATCH 092/101] add FFT and tests (#847) --- algorithms/maths/fft.py | 32 ++++++++++++++++++++++++++++++++ tests/test_maths.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 algorithms/maths/fft.py diff --git a/algorithms/maths/fft.py b/algorithms/maths/fft.py new file mode 100644 index 000000000..6f6697421 --- /dev/null +++ b/algorithms/maths/fft.py @@ -0,0 +1,32 @@ +""" +Implementation of the Cooley-Tukey, which is the most common FFT algorithm. + +Input: an array of complex values which has a size of N, where N is an integer power of 2 +Output: an array of complex values which is the discrete fourier transform of the input + +Example 1 +Input: [2.0+2j, 1.0+3j, 3.0+1j, 2.0+2j] +Output: [8+8j, 2j, 2-2j, -2+0j] + + +Pseudocode: https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm +""" +from cmath import exp, pi + +def fft(x): + """ Recursive implementation of the Cooley-Tukey""" + N = len(x) + if N == 1: + return x + + # get the elements at even/odd indices + even = fft(x[0::2]) + odd = fft(x[1::2]) + + y = [0 for i in range(N)] + for k in range(N//2): + q = exp(-2j*pi*k/N)*odd[k] + y[k] = even[k] + q + y[k + N//2] = even[k] - q + + return y diff --git a/tests/test_maths.py b/tests/test_maths.py index 032145556..c4a54af03 100644 --- a/tests/test_maths.py +++ b/tests/test_maths.py @@ -26,6 +26,7 @@ diffie_hellman_key_exchange, krishnamurthy_number, num_perfect_squares, chinese_remainder_theorem, + fft ) import unittest @@ -556,5 +557,41 @@ def test_empty_lists(self): chinese_remainder_theorem.solve_chinese_remainder(num, rem) +class TestFFT(unittest.TestCase): + """[summary] + Test for the file fft.py + + Arguments: + unittest {[type]} -- [description] + """ + def test_real_numbers(self): + x = [1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0] + y = [4.000, 2.613, 0.000, 1.082, 0.000, 1.082, 0.000, 2.613] + # abs(complex) returns the magnitude + result = [float("%.3f" % abs(f)) for f in fft.fft(x)] + self.assertEqual(result, y) + + def test_all_zero(self): + x = [0.0, 0.0, 0.0, 0.0] + y = [0.0, 0.0, 0.0, 0.0] + result = [float("%.1f" % abs(f)) for f in fft.fft(x)] + self.assertEqual(result, y) + + def test_all_ones(self): + x = [1.0, 1.0, 1.0, 1.0] + y = [4.0, 0.0, 0.0, 0.0] + result = [float("%.1f" % abs(f)) for f in fft.fft(x)] + self.assertEqual(result, y) + + def test_complex_numbers(self): + x = [2.0+2j, 1.0+3j, 3.0+1j, 2.0+2j] + real = [8.0, 0.0, 2.0, -2.0] + imag = [8.0, 2.0, -2.0, 0.0] + realResult = [float("%.1f" % f.real) for f in fft.fft(x)] + imagResult = [float("%.1f" % f.imag) for f in fft.fft(x)] + self.assertEqual(real, realResult) + self.assertEqual(imag, imagResult) + + if __name__ == "__main__": unittest.main() From c0e5404a3303a4def1816f336af10eb56fd6a19c Mon Sep 17 00:00:00 2001 From: psalqvist <63300368+psalqvist@users.noreply.github.com> Date: Wed, 8 Mar 2023 10:35:29 +0100 Subject: [PATCH 093/101] feat: add dynamic programming algorithm to strings.min_distance.py (#838) also added a small unit test to ensure the algorithm is correct fixes: #15 Co-authored-by: Philip Salqvist --- algorithms/strings/min_distance.py | 46 ++++++++++++++++++++++++++---- tests/test_strings.py | 8 ++++++ 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/algorithms/strings/min_distance.py b/algorithms/strings/min_distance.py index 4e80f471f..b63f5db2a 100644 --- a/algorithms/strings/min_distance.py +++ b/algorithms/strings/min_distance.py @@ -12,17 +12,51 @@ """ def min_distance(word1, word2): + """ + Finds minimum distance by getting longest common subsequence + + :type word1: str + :type word2: str + :rtype: int + """ return len(word1) + len(word2) - 2 * lcs(word1, word2, len(word1), len(word2)) -def lcs(s1, s2, i, j): +def lcs(word1, word2, i, j): """ - The length of longest common subsequence among the two given strings s1 and s2 + The length of longest common subsequence among the two given strings word1 and word2 """ if i == 0 or j == 0: return 0 - elif s1[i - 1] == s2[j - 1]: - return 1 + lcs(s1, s2, i - 1, j - 1) + if word1[i - 1] == word2[j - 1]: + return 1 + lcs(word1, word2, i - 1, j - 1) + return max(lcs(word1, word2, i - 1, j), lcs(word1, word2, i, j - 1)) + +def min_distance_dp(word1, word2): + """ + Finds minimum distance in a dynamic programming manner + TC: O(length1*length2), SC: O(length1*length2) + + :type word1: str + :type word2: str + :rtype: int + """ + length1, length2 = len(word1)+1, len(word2)+1 + res = [[0 for _ in range(length2)] for _ in range(length1)] + + if length1 == length2: + for i in range(1, length1): + res[i][0], res[0][i] = i, i else: - return max(lcs(s1, s2, i - 1, j), lcs(s1, s2, i, j - 1)) + for i in range(length1): + res[i][0] = i + for i in range(length2): + res[0][i] = i + + for i in range(1, length1): + for j in range(1, length2): + if word1[i-1] == word2[j-1]: + res[i][j] = res[i-1][j-1] + else: + res[i][j] = min(res[i-1][j], res[i][j-1]) + 1 -# TODO: Using dynamic programming + return res[len(word1)][len(word2)] diff --git a/tests/test_strings.py b/tests/test_strings.py index 57f5ff0c1..e7a68302a 100644 --- a/tests/test_strings.py +++ b/tests/test_strings.py @@ -32,6 +32,7 @@ repeat_string, text_justification, min_distance, + min_distance_dp, longest_common_prefix_v1, longest_common_prefix_v2, longest_common_prefix_v3, rotate, rotate_alt, @@ -535,6 +536,13 @@ class TestMinDistance(unittest.TestCase): def test_min_distance(self): self.assertEqual(2, min_distance("sea", "eat")) self.assertEqual(6, min_distance("abAlgocrithmf", "Algorithmmd")) + self.assertEqual(4, min_distance("acbbd", "aabcd")) + +class TestMinDistanceDP(unittest.TestCase): + def test_min_distance(self): + self.assertEqual(2, min_distance_dp("sea", "eat")) + self.assertEqual(6, min_distance_dp("abAlgocrithmf", "Algorithmmd")) + self.assertEqual(4, min_distance("acbbd", "aabcd")) class TestLongestCommonPrefix(unittest.TestCase): From a336ee8f6595ad0ef14de6acbae765b36af7c5ba Mon Sep 17 00:00:00 2001 From: Anujeet Kunturkar <89986435+Anujeet23@users.noreply.github.com> Date: Tue, 4 Apr 2023 18:48:36 +0530 Subject: [PATCH 094/101] Update binary_search.py (#902) * Update binary_search.py * Update first_occurrence.py --- algorithms/search/binary_search.py | 13 +++++++------ algorithms/search/first_occurrence.py | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/algorithms/search/binary_search.py b/algorithms/search/binary_search.py index 6b398764c..2a7c9bc3e 100644 --- a/algorithms/search/binary_search.py +++ b/algorithms/search/binary_search.py @@ -34,18 +34,19 @@ def binary_search(array, query): high = mid - 1 return None +#In this below function we are passing array, it's first index , last index and value to be searched def binary_search_recur(array, low, high, val): """ Worst-case Complexity: O(log(n)) reference: https://en.wikipedia.org/wiki/Binary_search_algorithm """ - - if low > high: # error case +#Here in Logic section first we are checking if low is greater than high which means its an error condition because low index should not move ahead of high index + if low > high: return -1 - mid = (low + high) // 2 - if val < array[mid]: - return binary_search_recur(array, low, mid - 1, val) + mid = low + (high-low)//2 #This mid will not break integer range + if val < array[mid]: + return binary_search_recur(array, low, mid - 1, val) #Go search in the left subarray if val > array[mid]: - return binary_search_recur(array, mid + 1, high, val) + return binary_search_recur(array, mid + 1, high, val) #Go search in the right subarray return mid diff --git a/algorithms/search/first_occurrence.py b/algorithms/search/first_occurrence.py index 119ba4df2..9d9beaae1 100644 --- a/algorithms/search/first_occurrence.py +++ b/algorithms/search/first_occurrence.py @@ -11,7 +11,7 @@ def first_occurrence(array, query): low, high = 0, len(array) - 1 while low <= high: - mid = (low + high) // 2 + mid = low + (high-low)//2 #Now mid will be ininteger range #print("lo: ", lo, " hi: ", hi, " mid: ", mid) if low == high: break From 1117ffe74b6c1232e3e048e6048819985c19be85 Mon Sep 17 00:00:00 2001 From: PIYUSH GOSWAMI Date: Tue, 6 Feb 2024 04:31:28 +0530 Subject: [PATCH 095/101] Optimize longest_non_repeat.py (#914) Added window sliding approach to find longest non repeating sub string --- algorithms/arrays/longest_non_repeat.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/algorithms/arrays/longest_non_repeat.py b/algorithms/arrays/longest_non_repeat.py index 786c03dfe..b25a952ca 100644 --- a/algorithms/arrays/longest_non_repeat.py +++ b/algorithms/arrays/longest_non_repeat.py @@ -88,4 +88,22 @@ def get_longest_non_repeat_v2(string): max_len = index - start + 1 sub_string = string[start: index + 1] used_char[char] = index - return max_len, sub_string \ No newline at end of file + return max_len, sub_string + +def get_longest_non_repeat_v3(string): + """ + Find the length of the longest substring + without repeating characters. + Uses window sliding approach. + Return max_len and the substring as a tuple + """ + longest_substring = '' + seen = set() + start_idx = 0 + for i in range(len(string)): + while string[i] in seen: + seen.remove(string[start_idx]) + start_idx += 1 + seen.add(string[i]) + longest_substring = max(longest_substring, string[start_idx: i+1], key=len) + return len(longest_substring), longest_substring From 40c944c0457168749f66095e9b7e86f85c3fa709 Mon Sep 17 00:00:00 2001 From: zlhanq <127005873+zlhanq@users.noreply.github.com> Date: Tue, 6 Feb 2024 07:01:59 +0800 Subject: [PATCH 096/101] Update summarize_ranges.py (#912) --- algorithms/arrays/summarize_ranges.py | 28 +++++++++++++-------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/algorithms/arrays/summarize_ranges.py b/algorithms/arrays/summarize_ranges.py index 8cfba68e1..58de7421a 100644 --- a/algorithms/arrays/summarize_ranges.py +++ b/algorithms/arrays/summarize_ranges.py @@ -6,22 +6,20 @@ """ -def summarize_ranges(array): - """ - :type array: List[int] - :rtype: List[] - """ +from typing import List + +def summarize_ranges(array: List[int]) -> List[str]: res = [] if len(array) == 1: return [str(array[0])] - i = 0 - while i < len(array): - num = array[i] - while i + 1 < len(array) and array[i + 1] - array[i] == 1: - i += 1 - if array[i] != num: - res.append((num, array[i])) + it = iter(array) + start = end = next(it) + for num in it: + if num - end == 1: + end = num else: - res.append((num, num)) - i += 1 - return res + res.append((start, end) if start != end else (start,)) + start = end = num + res.append((start, end) if start != end else (start,)) + return [f"{r[0]}-{r[1]}" if len(r) > 1 else str(r[0]) for r in res] + From e9c28af6d0384f91c96c293ec3a1d05b6f61b0ea Mon Sep 17 00:00:00 2001 From: Rubal Singh Date: Tue, 6 Feb 2024 04:32:18 +0530 Subject: [PATCH 097/101] Add Kosaraju algorithm (#910) * added kosaraju's algorithm under /algorithms/graph * added test case for /algorithms/graph/strongly_connected_component_kosaraju --------- Co-authored-by: Rubal Singh --- .../strongly_connected_components_kosaraju.py | 81 +++++++++++++++++++ tests/test_graph.py | 17 ++++ 2 files changed, 98 insertions(+) create mode 100644 algorithms/graph/strongly_connected_components_kosaraju.py diff --git a/algorithms/graph/strongly_connected_components_kosaraju.py b/algorithms/graph/strongly_connected_components_kosaraju.py new file mode 100644 index 000000000..0c82a7af6 --- /dev/null +++ b/algorithms/graph/strongly_connected_components_kosaraju.py @@ -0,0 +1,81 @@ +""" +Implementing strongly connected components in a graph using Kosaraju's algorithm. +https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm +""" + + +class Kosaraju: + """ + Kosaraju's algorithm use depth first search approach to find strongly connected components in a directed graph. + Approach: + 1. Make a DFS call to keep track of finish time of each vertex. + 2. Tranpose the original graph. ie 1->2 transpose is 1<-2 + 3. Make another DFS call to calculate strongly connected components. + """ + + def dfs(self, i, V, adj, visited, stk): + visited[i] = 1 + + for x in adj[i]: + if visited[x] == -1: + self.dfs(x, V, adj, visited, stk) + + stk.append(i) + + def kosaraju(self, V, adj): + + stk, visited = [], [-1]*(V+1) + + for i in range(V): + if visited[i] == -1: + self.dfs(i, V, adj, visited, stk) + + stk.reverse() + res = stk.copy() + + ans, visited1 = 0, [-1]*(V+1) + + adj1 = [[] for x in range(V)] + + for i in range(len(adj)): + for x in adj[i]: + adj1[x].append(i) + + for i in range(len(res)): + if visited1[res[i]] == -1: + ans += 1 + self.dfs(res[i], V, adj1, visited1, stk) + + return ans + + +def main(): + """ + Let's look at the sample input. + + 6 7 #no of vertex, no of edges + 0 2 #directed edge 0->2 + 1 0 + 2 3 + 3 1 + 3 4 + 4 5 + 5 4 + + calculating no of strongly connected compnenets in a directed graph. + answer should be: 2 + 1st strong component: 0->2->3->1->0 + 2nd strongly connected component: 4->5->4 + """ + V, E = map(int, input().split()) + adj = [[] for x in range(V)] + + for i in range(E): + u, v = map(int, input().split()) + adj[u].append(v) + + print(Kosaraju().kosaraju(V, adj)) + + +if __name__ == '__main__': + main() diff --git a/tests/test_graph.py b/tests/test_graph.py index eb50dd4e1..540d29983 100644 --- a/tests/test_graph.py +++ b/tests/test_graph.py @@ -14,6 +14,7 @@ from algorithms.graph import cycle_detection from algorithms.graph import find_path from algorithms.graph import path_between_two_vertices_in_digraph +from algorithms.graph import strongly_connected_components_kosaraju import unittest @@ -349,3 +350,19 @@ def test_node_is_reachable(self): self.assertTrue(g.is_reachable(1, 3)) self.assertFalse(g.is_reachable(3, 1)) +class TestStronglyConnectedComponentsKosaraju(unittest.TestCase): + def test_kosaraju_algorithm(self): + V = 6 + adj = [ + [2], + [0], + [3], + [1, 4], + [5], + [4] + ] + + result = strongly_connected_components_kosaraju.Kosaraju().kosaraju(V, adj) + + # Expected result: 2 strongly connected components + self.assertEqual(result, 2) From cad4754bc71742c2d6fcbd3b92ae74834d359844 Mon Sep 17 00:00:00 2001 From: oDqnger <103481200+oDqnger@users.noreply.github.com> Date: Mon, 5 Feb 2024 23:03:25 +0000 Subject: [PATCH 098/101] Add remove duplicates (#905) * Initial commit for remove duplicates * Made changes to readme and added test case --- README.md | 1 + algorithms/arrays/__init__.py | 1 + algorithms/arrays/remove_duplicates.py | 18 ++++++++++++++++++ tests/test_array.py | 10 ++++++++++ 4 files changed, 30 insertions(+) create mode 100644 algorithms/arrays/remove_duplicates.py diff --git a/README.md b/README.md index 0ec23733f..65caeb5ca 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ If you want to uninstall algorithms, it is as simple as: - [merge_intervals](algorithms/arrays/merge_intervals.py) - [missing_ranges](algorithms/arrays/missing_ranges.py) - [plus_one](algorithms/arrays/plus_one.py) + - [remove_duplicates](algorithms/arrays/remove_duplicates.py) - [rotate](algorithms/arrays/rotate.py) - [summarize_ranges](algorithms/arrays/summarize_ranges.py) - [three_sum](algorithms/arrays/three_sum.py) diff --git a/algorithms/arrays/__init__.py b/algorithms/arrays/__init__.py index 9670db750..2632ca1c7 100644 --- a/algorithms/arrays/__init__.py +++ b/algorithms/arrays/__init__.py @@ -16,3 +16,4 @@ from .two_sum import * from .limit import * from .n_sum import * +from .remove_duplicates import * \ No newline at end of file diff --git a/algorithms/arrays/remove_duplicates.py b/algorithms/arrays/remove_duplicates.py new file mode 100644 index 000000000..1c0bc0a06 --- /dev/null +++ b/algorithms/arrays/remove_duplicates.py @@ -0,0 +1,18 @@ +""" +This algorithm removes any duplicates from an array and returns a new array with those duplicates +removed. + +For example: + +Input: [1, 1 ,1 ,2 ,2 ,3 ,4 ,4 ,"hey", "hey", "hello", True, True] +Output: [1, 2, 3, 4, 'hey', 'hello'] +""" + +def remove_duplicates(array): + new_array = [] + + for item in array: + if item not in new_array: + new_array.append(item) + + return new_array \ No newline at end of file diff --git a/tests/test_array.py b/tests/test_array.py index eaabfc710..f1ad11693 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -9,6 +9,7 @@ missing_ranges, move_zeros, plus_one_v1, plus_one_v2, plus_one_v3, + remove_duplicates rotate_v1, rotate_v2, rotate_v3, summarize_ranges, three_sum, @@ -298,6 +299,15 @@ def test_plus_one_v3(self): self.assertListEqual(plus_one_v3([9, 9, 9, 9]), [1, 0, 0, 0, 0]) +class TestRemoveDuplicate(unittest.TestCase): + + def test_remove_duplicates(self): + self.assertListEqual(remove_duplicates([1,1,1,2,2,2,3,3,4,4,5,6,7,7,7,8,8,9,10,10])) + self.assertListEqual(remove_duplicates(["hey", "hello", "hello", "car", "house", "house"])) + self.assertListEqual(remove_duplicates([True, True, False, True, False, None, None])) + self.assertListEqual(remove_duplicates([1,1,"hello", "hello", True, False, False])) + self.assertListEqual(remove_duplicates([1, "hello", True, False])) + class TestRotateArray(unittest.TestCase): From 66eb36d27721676000f9a656a2fa3987963de176 Mon Sep 17 00:00:00 2001 From: Canderton74 <108482522+Canderton74@users.noreply.github.com> Date: Fri, 27 Jun 2025 01:29:58 -0400 Subject: [PATCH 099/101] Fixed polynomial division (#2130) --- algorithms/maths/polynomial.py | 79 ++++++++++++++++++++++++++-------- tests/test_polynomial.py | 70 ++++++++++++++++++++---------- 2 files changed, 109 insertions(+), 40 deletions(-) diff --git a/algorithms/maths/polynomial.py b/algorithms/maths/polynomial.py index 07a0c3b8a..81faf2b7e 100644 --- a/algorithms/maths/polynomial.py +++ b/algorithms/maths/polynomial.py @@ -438,10 +438,7 @@ def __floordiv__(self, other: Union[int, float, Fraction, Monomial]): # def __truediv__(self, other: Union[int, float, Fraction, Monomial, Polynomial]) -> Polynomial: def __truediv__(self, other: Union[int, float, Fraction, Monomial]): """ - For Polynomials, only division by a monomial - is defined. - - TODO: Implement polynomial / polynomial. + For Polynomial division, no remainder is provided. Must use poly_long_division() to capture remainder """ if isinstance(other, int) or isinstance(other, float) or isinstance(other, Fraction): return self.__truediv__( Monomial({}, other) ) @@ -449,17 +446,11 @@ def __truediv__(self, other: Union[int, float, Fraction, Monomial]): poly_temp = reduce(lambda acc, val: acc + val, map(lambda x: x / other, [z for z in self.all_monomials()]), Polynomial([Monomial({}, 0)])) return poly_temp elif isinstance(other, Polynomial): - if Monomial({}, 0) in other.all_monomials(): - if len(other.all_monomials()) == 2: - temp_set = {x for x in other.all_monomials() if x != Monomial({}, 0)} - only = temp_set.pop() - return self.__truediv__(only) - elif len(other.all_monomials()) == 1: - temp_set = {x for x in other.all_monomials()} - only = temp_set.pop() - return self.__truediv__(only) - - raise ValueError('Can only divide a polynomial by an int, float, Fraction, or a Monomial.') + # Call long division + quotient, remainder = self.poly_long_division(other) + return quotient # Return just the quotient, remainder is ignored here + + raise ValueError('Can only divide a polynomial by an int, float, Fraction, Monomial, or Polynomial.') return @@ -526,7 +517,59 @@ def subs(self, substitutions: Union[int, float, Fraction, Dict[int, Union[int, f def __str__(self) -> str: """ - Get a string representation of - the polynomial. + Get a properly formatted string representation of the polynomial. + """ + sorted_monos = sorted(self.all_monomials(), key=lambda m: sorted(m.variables.items(), reverse=True), + reverse=True) + return ' + '.join(str(m) for m in sorted_monos if m.coeff != Fraction(0, 1)) + + def poly_long_division(self, other: 'Polynomial') -> tuple['Polynomial', 'Polynomial']: """ - return ' + '.join(str(m) for m in self.all_monomials() if m.coeff != Fraction(0, 1)) + Perform polynomial long division + Returns (quotient, remainder) + """ + if not isinstance(other, Polynomial): + raise ValueError("Can only divide by another Polynomial.") + + if len(other.all_monomials()) == 0: + raise ValueError("Cannot divide by zero polynomial.") + + quotient = Polynomial([]) + remainder = self.clone() + + divisor_monos = sorted(other.all_monomials(), key=lambda m: sorted(m.variables.items(), reverse=True), + reverse=True) + divisor_lead = divisor_monos[0] + + while remainder.all_monomials() and max(remainder.variables(), default=-1) >= max(other.variables(), + default=-1): + remainder_monos = sorted(remainder.all_monomials(), key=lambda m: sorted(m.variables.items(), reverse=True), + reverse=True) + remainder_lead = remainder_monos[0] + + if not all(remainder_lead.variables.get(var, 0) >= divisor_lead.variables.get(var, 0) for var in + divisor_lead.variables): + break + + lead_quotient = remainder_lead / divisor_lead + quotient = quotient + Polynomial([lead_quotient]) # Convert Monomial to Polynomial + + remainder = remainder - ( + Polynomial([lead_quotient]) * other) # Convert Monomial to Polynomial before multiplication + + return quotient, remainder + +dividend = Polynomial([ + Monomial({1: 3}, 4), # 4(a_1)^3 + Monomial({1: 2}, 3), # 3(a_1)^2 + Monomial({1: 1}, -2), # -2(a_1) + Monomial({}, 5) # +5 +]) + +divisor = Polynomial([ + Monomial({1: 1}, 2), # 2(a_1) + Monomial({}, -1) # -1 +]) + +quotient = dividend / divisor +print("Quotient:", quotient) diff --git a/tests/test_polynomial.py b/tests/test_polynomial.py index 4ba2ba6b7..ab5674017 100644 --- a/tests/test_polynomial.py +++ b/tests/test_polynomial.py @@ -105,27 +105,6 @@ def test_polynomial_multiplication(self): ])) return - def test_polynomial_division(self): - - # Should raise a ValueError if the divisor is not a monomial - # or a polynomial with only one term. - self.assertRaises(ValueError, lambda x, y: x / y, self.p5, self.p3) - self.assertRaises(ValueError, lambda x, y: x / y, self.p6, self.p4) - - self.assertEqual(self.p3 / self.p2, Polynomial([ - Monomial({}, 1), - Monomial({1: 1, 2: -1}, 0.75) - ])) - self.assertEqual(self.p7 / self.m1, Polynomial([ - Monomial({1: -1, 2: -3}, 2), - Monomial({1: 0, 2: -4}, 1.5) - ])) - self.assertEqual(self.p7 / self.m1, Polynomial([ - Monomial({1: -1, 2: -3}, 2), - Monomial({2: -4}, 1.5) - ])) - return - def test_polynomial_variables(self): # The zero polynomial has no variables. @@ -172,4 +151,51 @@ def test_polynomial_clone(self): self.assertEqual(self.p5.clone(), Polynomial([ Monomial({1: -1, 3: 2}, 1) ])) - return \ No newline at end of file + return + + def test_polynomial_long_division(self): + """ + Test polynomial long division + """ + + # Dividend: 4a_1^3 + 3a_1^2 - 2a_1 + 5 + dividend = Polynomial([ + Monomial({1: 3}, 4), # 4(a_1)^3 + Monomial({1: 2}, 3), # 3(a_1)^2 + Monomial({1: 1}, -2), # -2(a_1) + Monomial({}, 5) # +5 + ]) + + # Divisor: 2a_1 - 1 + divisor = Polynomial([ + Monomial({1: 1}, 2), # 2(a_1) + Monomial({}, -1) # -1 + ]) + + # Expected Quotient: 2a_1^2 + (5/2)a_1 + 1/4 + expected_quotient = Polynomial([ + Monomial({1: 2}, 2), # 2(a_1)^2 + Monomial({1: 1}, Fraction(5, 2)), # (5/2)(a_1) + Monomial({}, Fraction(1, 4)) # +1/4 + ]) + + # Expected Remainder: 21/4 + expected_remainder = Polynomial([ + Monomial({}, Fraction(21, 4)) # 21/4 + ]) + + quotient_long_div, remainder_long_div = dividend.poly_long_division(divisor) + + quotient_truediv = dividend / divisor # Calls __truediv__, which returns only the quotient + + # Check if quotient from poly_long_division matches expected + self.assertEqual(quotient_long_div, expected_quotient) + + # Check if remainder from poly_long_division matches expected + self.assertEqual(remainder_long_div, expected_remainder) + + # Check if quotient from __truediv__ matches quotient from poly_long_division + self.assertEqual(quotient_truediv, quotient_long_div) + + return + From 0b04e60364fe02dc04c5232f274286bcbbee1049 Mon Sep 17 00:00:00 2001 From: Chadndrabhan Patel <88890660+cpatel321@users.noreply.github.com> Date: Fri, 27 Jun 2025 11:00:17 +0530 Subject: [PATCH 100/101] Update test_array.py (missing comma at line 12) (#2047) --- tests/test_array.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_array.py b/tests/test_array.py index f1ad11693..b73ecb17b 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -9,7 +9,7 @@ missing_ranges, move_zeros, plus_one_v1, plus_one_v2, plus_one_v3, - remove_duplicates + remove_duplicates, rotate_v1, rotate_v2, rotate_v3, summarize_ranges, three_sum, From 486fa37782e956c28449b109ba6864ceb78a3972 Mon Sep 17 00:00:00 2001 From: shirleymaza <166472849+shirleymaza@users.noreply.github.com> Date: Fri, 27 Jun 2025 01:30:32 -0400 Subject: [PATCH 101/101] Added Bead Sort Algorithm #920 (#2100) --- algorithms/sort/bead_sort.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 algorithms/sort/bead_sort.py diff --git a/algorithms/sort/bead_sort.py b/algorithms/sort/bead_sort.py new file mode 100644 index 000000000..0d949fa82 --- /dev/null +++ b/algorithms/sort/bead_sort.py @@ -0,0 +1,26 @@ +""" +Bead Sort (also known as Gravity Sort) is a natural sorting algorithm that simulates how beads would settle under gravity on an abacus. It is most useful for sorting positive integers, especially when the range of numbers isn't excessively large. However, it is not a comparison-based sort and is generally impractical for large inputs due to its reliance on physical modeling. +Time Complexity +- Best Case: O(n) if the numbers are already sorted +- Average Case: O(n^2) because each bead needs to be placed and then fall under gravity +- Worst Case: O(n^2) since each bead must "fall" individually +""" + +def bead_sort(arr): + if any(num < 0 for num in arr): + raise ValueError("Bead sort only works with non-negative integers.") + + max_num = max(arr) if arr else 0 + grid = [[0] * len(arr) for _ in range(max_num)] + + # Drop beads (place beads in columns) + for col, num in enumerate(arr): + for row in range(num): + grid[row][col] = 1 + + # Let the beads "fall" (count beads in each row) + for row in grid: + sum_beads = sum(row) + for col in range(len(arr)): + row[col] = 1 if col < sum_beads else 0 + pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy

Alternative Proxy