From 859a987981f244d2a4070c2435bc2c6278b9af09 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 28 Dec 2019 14:26:43 +0200 Subject: [PATCH 1/4] Added Strassen divide and conquer algorithm to multiply matrices --- .../strassen_matrix_multiplication.py | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 divide_and_conquer/strassen_matrix_multiplication.py diff --git a/divide_and_conquer/strassen_matrix_multiplication.py b/divide_and_conquer/strassen_matrix_multiplication.py new file mode 100644 index 000000000000..0cc77dadee96 --- /dev/null +++ b/divide_and_conquer/strassen_matrix_multiplication.py @@ -0,0 +1,126 @@ +import math + +def default_matrix_multiplication(a, b): + """ + Multiplication only for 2x2 matrices + """ + if len(a) != 2 or len(a[0]) != 2 or len(b) != 2 or len(b[0]) != 2: + raise Exception('Matrices are not 2x2') + new_matrix = [[a[0][0] * b[0][0] + a[0][1] * b[1][0], a[0][0] * b[0][1] + a[0][1] * b[1][1]], + [a[1][0] * b[0][0] + a[1][1] * b[1][0], a[1][0] * b[0][1] + a[1][1] * b[1][1]]] + return new_matrix + +def matrix_addition(matrix_a, matrix_b): + return [[matrix_a[row][col] + matrix_b[row][col] for col in range(len(matrix_a[row]))] for row in range(len(matrix_a))] + +def matrix_subtraction(matrix_a, matrix_b): + return [[matrix_a[row][col] - matrix_b[row][col] for col in range(len(matrix_a[row]))] for row in range(len(matrix_a))] + + +def split_matrix(a): + """ + Given an even length matrix, returns the top_left, top_right, bot_left, bot_right quadrant. + """ + if len(a) % 2 != 0 or len(a[0]) % 2 != 0: + raise Exception('Odd matrices are not supported!') + + matrix_length = len(a) + mid = matrix_length // 2 + + top_right = [[a[i][j] for j in range(mid, matrix_length)] for i in range(mid)] + bot_right = [[a[i][j] for j in range(mid, matrix_length)] for i in range(mid, matrix_length)] + + top_left = [[a[i][j] for j in range(mid)] for i in range(mid)] + bot_left = [[a[i][j] for j in range(mid)] for i in range(mid, matrix_length)] + + + + return top_left, top_right, bot_left, bot_right + +def matrix_dimensions(matrix): + return len(matrix), len(matrix[0]) + + +def strassen(matrix_a, matrix_b): + """ + Recursive function to calculate the product of two matrices, using the Strassen Algorithm. + It only supports even length matrices. + """ + if matrix_dimensions(matrix_a) == (2, 2): + return default_matrix_multiplication(matrix_a, matrix_b) + + a,b,c,d = split_matrix(matrix_a) + e,f,g,h = split_matrix(matrix_b) + + t1 = strassen(a, matrix_subtraction(f, h)) + t2 = strassen(matrix_addition(a, b), h) + t3 = strassen(matrix_addition(c, d), e) + t4 = strassen(d, matrix_subtraction(g, e)) + t5 = strassen(matrix_addition(a, d), matrix_addition(e, h)) + t6 = strassen(matrix_subtraction(b, d), matrix_addition(g, h)) + t7 = strassen(matrix_subtraction(a, c), matrix_addition(e, f)) + + top_left = matrix_addition(matrix_subtraction(matrix_addition(t5, t4), t2), t6) + top_right = matrix_addition(t1, t2) + bot_left = matrix_addition(t3, t4) + bot_right = matrix_subtraction(matrix_subtraction(matrix_addition(t1, t5), t3), t7) + + # construct the new matrix from our 4 quadrants + new_matrix = [] + for i in range(len(top_right)): + new_matrix.append(top_left[i] + top_right[i]) + for i in range(len(bot_right)): + new_matrix.append(bot_left[i] + bot_right[i]) + return new_matrix + +def print_matrix(matrix): + for i in range(len(matrix)): + print(matrix[i]) + +def multiply_matrices(matrix1, matrix2): + if matrix_dimensions(matrix1)[1] != matrix_dimensions(matrix2)[0]: + raise Exception(f'Unable to multiply these matrices, please check the dimensions. \nMatrix A:{matrix1} \nMatrix B:{matrix2}') + dimension1 = matrix_dimensions(matrix1) + dimension2 = matrix_dimensions(matrix2) + + if dimension1[0] == dimension1[1] and dimension2[0] == dimension2[1]: + return matrix1, matrix2 + + maximum = max(max(dimension1), max(dimension2)) + maxim = int(math.pow(2, math.ceil(math.log2(maximum)))) + print(max) + new_matrix1 = matrix1 + new_matrix2 = matrix2 + """ + Adding zeros to the matrices so that the arrays dimensions are the same and also power of 2 + """ + for i in range(0,maxim): + if i < dimension1[0]: + for j in range(dimension1[1],maxim): + new_matrix1[i].append(0) + else: + new_matrix1.append([0] * maxim) + if i < dimension2[0]: + for j in range(dimension2[1],maxim): + new_matrix2[i].append(0) + else: + new_matrix2.append([0] * maxim) + + final_matrix = strassen(new_matrix1, new_matrix2) + + """ + Removing the additional zeros + """ + for i in range(0,maxim): + if i < dimension1[0]: + for j in range(dimension2[1],maxim): + final_matrix[i].pop() + else: + final_matrix.pop() + return final_matrix + + +if __name__ == '__main__': + matrix1 = [[2,3,4,5],[6,4,3,1],[2,3,6,7],[3,1,2,4],[2,3,4,5],[6,4,3,1],[2,3,6,7],[3,1,2,4],[2,3,4,5],[6,2,3,1]] + matrix2 = [[0,2,1,1],[16,2,3,3],[2,2,7,7],[13,11,22,4]] + print_matrix(multiply_matrices(matrix1,matrix2)) From 437254b574c303de318a284cd88f9341150d6bff Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 28 Dec 2019 15:01:18 +0200 Subject: [PATCH 2/4] Divide and conquer algorith to calculate pow(a,b) or a raised to the power of b --- divide_and_conquer/power.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 divide_and_conquer/power.py diff --git a/divide_and_conquer/power.py b/divide_and_conquer/power.py new file mode 100644 index 000000000000..403d92026d0f --- /dev/null +++ b/divide_and_conquer/power.py @@ -0,0 +1,15 @@ + +""" +Function using divide and conquer to calculate a^b. +It only works for integer a,b. +""" +def power(a,b): + if b == 0: + return 1 + elif ( (b%2) == 0 ): + return (power(a,int(b/2)) * power(a,int(b/2))) + else: + return (a * power(a,int(b/2)) * power(a,int(b/2))) + +if __name__ == "__main__": + print(power(2,1000000)) From 59bf2c6204535c26a220b91e03c191f9df169c63 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 28 Dec 2019 15:07:31 +0200 Subject: [PATCH 3/4] Putting docstring inside the function. --- divide_and_conquer/power.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/divide_and_conquer/power.py b/divide_and_conquer/power.py index 403d92026d0f..b1577d3e7278 100644 --- a/divide_and_conquer/power.py +++ b/divide_and_conquer/power.py @@ -1,9 +1,10 @@ -""" -Function using divide and conquer to calculate a^b. -It only works for integer a,b. -""" + def power(a,b): + """ + Function using divide and conquer to calculate a^b. + It only works for integer a,b. + """ if b == 0: return 1 elif ( (b%2) == 0 ): From 7fd75fc10fa3272117816dcb4342a62bffe65801 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 28 Dec 2019 19:18:12 +0200 Subject: [PATCH 4/4] Added doctests --- divide_and_conquer/power.py | 31 ++++- .../strassen_matrix_multiplication.py | 127 +++++++++++------- 2 files changed, 105 insertions(+), 53 deletions(-) diff --git a/divide_and_conquer/power.py b/divide_and_conquer/power.py index b1577d3e7278..f2e023afd536 100644 --- a/divide_and_conquer/power.py +++ b/divide_and_conquer/power.py @@ -1,16 +1,33 @@ - - -def power(a,b): +def actual_power(a: int, b: int): """ Function using divide and conquer to calculate a^b. It only works for integer a,b. """ if b == 0: return 1 - elif ( (b%2) == 0 ): - return (power(a,int(b/2)) * power(a,int(b/2))) + if (b % 2) == 0: + return actual_power(a, int(b / 2)) * actual_power(a, int(b / 2)) else: - return (a * power(a,int(b/2)) * power(a,int(b/2))) + return a * actual_power(a, int(b / 2)) * actual_power(a, int(b / 2)) + + +def power(a: int, b: int) -> float: + """ + >>> power(4,6) + 4096 + >>> power(2,3) + 8 + >>> power(-2,3) + -8 + >>> power(2,-3) + 0.125 + >>> power(-2,-3) + -0.125 + """ + if b < 0: + return 1 / actual_power(a, b) + return actual_power(a, b) + if __name__ == "__main__": - print(power(2,1000000)) + print(power(-2, -3)) diff --git a/divide_and_conquer/strassen_matrix_multiplication.py b/divide_and_conquer/strassen_matrix_multiplication.py index 0cc77dadee96..c0725b1c951f 100644 --- a/divide_and_conquer/strassen_matrix_multiplication.py +++ b/divide_and_conquer/strassen_matrix_multiplication.py @@ -1,47 +1,70 @@ import math +from typing import List, Tuple -def default_matrix_multiplication(a, b): + +def default_matrix_multiplication(a: List, b: List) -> List: """ Multiplication only for 2x2 matrices """ if len(a) != 2 or len(a[0]) != 2 or len(b) != 2 or len(b[0]) != 2: - raise Exception('Matrices are not 2x2') - new_matrix = [[a[0][0] * b[0][0] + a[0][1] * b[1][0], a[0][0] * b[0][1] + a[0][1] * b[1][1]], - [a[1][0] * b[0][0] + a[1][1] * b[1][0], a[1][0] * b[0][1] + a[1][1] * b[1][1]]] + raise Exception("Matrices are not 2x2") + new_matrix = [ + [a[0][0] * b[0][0] + a[0][1] * b[1][0], a[0][0] * b[0][1] + a[0][1] * b[1][1]], + [a[1][0] * b[0][0] + a[1][1] * b[1][0], a[1][0] * b[0][1] + a[1][1] * b[1][1]], + ] return new_matrix -def matrix_addition(matrix_a, matrix_b): - return [[matrix_a[row][col] + matrix_b[row][col] for col in range(len(matrix_a[row]))] for row in range(len(matrix_a))] -def matrix_subtraction(matrix_a, matrix_b): - return [[matrix_a[row][col] - matrix_b[row][col] for col in range(len(matrix_a[row]))] for row in range(len(matrix_a))] +def matrix_addition(matrix_a: List, matrix_b: List): + return [ + [matrix_a[row][col] + matrix_b[row][col] for col in range(len(matrix_a[row]))] + for row in range(len(matrix_a)) + ] + + +def matrix_subtraction(matrix_a: List, matrix_b: List): + return [ + [matrix_a[row][col] - matrix_b[row][col] for col in range(len(matrix_a[row]))] + for row in range(len(matrix_a)) + ] -def split_matrix(a): +def split_matrix(a: List,) -> Tuple[List, List, List, List]: """ Given an even length matrix, returns the top_left, top_right, bot_left, bot_right quadrant. + + >>> split_matrix([[4,3,2,4],[2,3,1,1],[6,5,4,3],[8,4,1,6]]) + ([[4, 3], [2, 3]], [[2, 4], [1, 1]], [[6, 5], [8, 4]], [[4, 3], [1, 6]]) + >>> split_matrix([[4,3,2,4,4,3,2,4],[2,3,1,1,2,3,1,1],[6,5,4,3,6,5,4,3],[8,4,1,6,8,4,1,6],[4,3,2,4,4,3,2,4],[2,3,1,1,2,3,1,1],[6,5,4,3,6,5,4,3],[8,4,1,6,8,4,1,6]]) + ([[4, 3, 2, 4], [2, 3, 1, 1], [6, 5, 4, 3], [8, 4, 1, 6]], [[4, 3, 2, 4], [2, 3, 1, 1], [6, 5, 4, 3], [8, 4, 1, 6]], [[4, 3, 2, 4], [2, 3, 1, 1], [6, 5, 4, 3], [8, 4, 1, 6]], [[4, 3, 2, 4], [2, 3, 1, 1], [6, 5, 4, 3], [8, 4, 1, 6]]) """ if len(a) % 2 != 0 or len(a[0]) % 2 != 0: - raise Exception('Odd matrices are not supported!') + raise Exception("Odd matrices are not supported!") matrix_length = len(a) mid = matrix_length // 2 top_right = [[a[i][j] for j in range(mid, matrix_length)] for i in range(mid)] - bot_right = [[a[i][j] for j in range(mid, matrix_length)] for i in range(mid, matrix_length)] + bot_right = [ + [a[i][j] for j in range(mid, matrix_length)] for i in range(mid, matrix_length) + ] top_left = [[a[i][j] for j in range(mid)] for i in range(mid)] bot_left = [[a[i][j] for j in range(mid)] for i in range(mid, matrix_length)] - - return top_left, top_right, bot_left, bot_right -def matrix_dimensions(matrix): + +def matrix_dimensions(matrix: List) -> Tuple[int, int]: return len(matrix), len(matrix[0]) -def strassen(matrix_a, matrix_b): +def print_matrix(matrix: List) -> None: + for i in range(len(matrix)): + print(matrix[i]) + + +def actual_strassen(matrix_a: List, matrix_b: List) -> List: """ Recursive function to calculate the product of two matrices, using the Strassen Algorithm. It only supports even length matrices. @@ -49,16 +72,16 @@ def strassen(matrix_a, matrix_b): if matrix_dimensions(matrix_a) == (2, 2): return default_matrix_multiplication(matrix_a, matrix_b) - a,b,c,d = split_matrix(matrix_a) - e,f,g,h = split_matrix(matrix_b) + a, b, c, d = split_matrix(matrix_a) + e, f, g, h = split_matrix(matrix_b) - t1 = strassen(a, matrix_subtraction(f, h)) - t2 = strassen(matrix_addition(a, b), h) - t3 = strassen(matrix_addition(c, d), e) - t4 = strassen(d, matrix_subtraction(g, e)) - t5 = strassen(matrix_addition(a, d), matrix_addition(e, h)) - t6 = strassen(matrix_subtraction(b, d), matrix_addition(g, h)) - t7 = strassen(matrix_subtraction(a, c), matrix_addition(e, f)) + t1 = actual_strassen(a, matrix_subtraction(f, h)) + t2 = actual_strassen(matrix_addition(a, b), h) + t3 = actual_strassen(matrix_addition(c, d), e) + t4 = actual_strassen(d, matrix_subtraction(g, e)) + t5 = actual_strassen(matrix_addition(a, d), matrix_addition(e, h)) + t6 = actual_strassen(matrix_subtraction(b, d), matrix_addition(g, h)) + t7 = actual_strassen(matrix_subtraction(a, c), matrix_addition(e, f)) top_left = matrix_addition(matrix_subtraction(matrix_addition(t5, t4), t2), t6) top_right = matrix_addition(t1, t2) @@ -73,13 +96,18 @@ def strassen(matrix_a, matrix_b): new_matrix.append(bot_left[i] + bot_right[i]) return new_matrix -def print_matrix(matrix): - for i in range(len(matrix)): - print(matrix[i]) -def multiply_matrices(matrix1, matrix2): +def strassen(matrix1: List, matrix2: List) -> List: + """ + >>> strassen([[2,1,3],[3,4,6],[1,4,2],[7,6,7]], [[4,2,3,4],[2,1,1,1],[8,6,4,2]]) + [[34, 23, 19, 15], [68, 46, 37, 28], [28, 18, 15, 12], [96, 62, 55, 48]] + >>> strassen([[3,7,5,6,9],[1,5,3,7,8],[1,4,4,5,7]], [[2,4],[5,2],[1,7],[5,5],[7,8]]) + [[139, 163], [121, 134], [100, 121]] + """ if matrix_dimensions(matrix1)[1] != matrix_dimensions(matrix2)[0]: - raise Exception(f'Unable to multiply these matrices, please check the dimensions. \nMatrix A:{matrix1} \nMatrix B:{matrix2}') + raise Exception( + f"Unable to multiply these matrices, please check the dimensions. \nMatrix A:{matrix1} \nMatrix B:{matrix2}" + ) dimension1 = matrix_dimensions(matrix1) dimension2 = matrix_dimensions(matrix2) @@ -88,39 +116,46 @@ def multiply_matrices(matrix1, matrix2): maximum = max(max(dimension1), max(dimension2)) maxim = int(math.pow(2, math.ceil(math.log2(maximum)))) - print(max) new_matrix1 = matrix1 new_matrix2 = matrix2 - """ - Adding zeros to the matrices so that the arrays dimensions are the same and also power of 2 - """ - for i in range(0,maxim): + + # Adding zeros to the matrices so that the arrays dimensions are the same and also power of 2 + for i in range(0, maxim): if i < dimension1[0]: - for j in range(dimension1[1],maxim): + for j in range(dimension1[1], maxim): new_matrix1[i].append(0) else: new_matrix1.append([0] * maxim) if i < dimension2[0]: - for j in range(dimension2[1],maxim): + for j in range(dimension2[1], maxim): new_matrix2[i].append(0) else: new_matrix2.append([0] * maxim) - final_matrix = strassen(new_matrix1, new_matrix2) + final_matrix = actual_strassen(new_matrix1, new_matrix2) - """ - Removing the additional zeros - """ - for i in range(0,maxim): + # Removing the additional zeros + for i in range(0, maxim): if i < dimension1[0]: - for j in range(dimension2[1],maxim): + for j in range(dimension2[1], maxim): final_matrix[i].pop() else: final_matrix.pop() return final_matrix -if __name__ == '__main__': - matrix1 = [[2,3,4,5],[6,4,3,1],[2,3,6,7],[3,1,2,4],[2,3,4,5],[6,4,3,1],[2,3,6,7],[3,1,2,4],[2,3,4,5],[6,2,3,1]] - matrix2 = [[0,2,1,1],[16,2,3,3],[2,2,7,7],[13,11,22,4]] - print_matrix(multiply_matrices(matrix1,matrix2)) +if __name__ == "__main__": + matrix1= [ + [2, 3, 4, 5], + [6, 4, 3, 1], + [2, 3, 6, 7], + [3, 1, 2, 4], + [2, 3, 4, 5], + [6, 4, 3, 1], + [2, 3, 6, 7], + [3, 1, 2, 4], + [2, 3, 4, 5], + [6, 2, 3, 1], + ] + matrix2 = [[0, 2, 1, 1], [16, 2, 3, 3], [2, 2, 7, 7], [13, 11, 22, 4]] + print(strassen(matrix1, matrix2))