forked from TheAlgorithms/Python
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added Strassen divide and conquer algorithm to multiply matrices. (Th…
…eAlgorithms#1648) * Added Strassen divide and conquer algorithm to multiply matrices * Divide and conquer algorith to calculate pow(a,b) or a raised to the power of b * Putting docstring inside the function. * Added doctests
- Loading branch information
1 parent
f9e1a16
commit 9873361
Showing
2 changed files
with
194 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
def actual_power(a: int, b: int): | ||
""" | ||
Function using divide and conquer to calculate a^b. | ||
It only works for integer a,b. | ||
""" | ||
if b == 0: | ||
return 1 | ||
if (b % 2) == 0: | ||
return actual_power(a, int(b / 2)) * actual_power(a, int(b / 2)) | ||
else: | ||
return a * actual_power(a, int(b / 2)) * actual_power(a, int(b / 2)) | ||
|
||
|
||
def power(a: int, b: int) -> float: | ||
""" | ||
>>> power(4,6) | ||
4096 | ||
>>> power(2,3) | ||
8 | ||
>>> power(-2,3) | ||
-8 | ||
>>> power(2,-3) | ||
0.125 | ||
>>> power(-2,-3) | ||
-0.125 | ||
""" | ||
if b < 0: | ||
return 1 / actual_power(a, b) | ||
return actual_power(a, b) | ||
|
||
|
||
if __name__ == "__main__": | ||
print(power(-2, -3)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
import math | ||
from typing import List, Tuple | ||
|
||
|
||
def default_matrix_multiplication(a: List, b: List) -> List: | ||
""" | ||
Multiplication only for 2x2 matrices | ||
""" | ||
if len(a) != 2 or len(a[0]) != 2 or len(b) != 2 or len(b[0]) != 2: | ||
raise Exception("Matrices are not 2x2") | ||
new_matrix = [ | ||
[a[0][0] * b[0][0] + a[0][1] * b[1][0], a[0][0] * b[0][1] + a[0][1] * b[1][1]], | ||
[a[1][0] * b[0][0] + a[1][1] * b[1][0], a[1][0] * b[0][1] + a[1][1] * b[1][1]], | ||
] | ||
return new_matrix | ||
|
||
|
||
def matrix_addition(matrix_a: List, matrix_b: List): | ||
return [ | ||
[matrix_a[row][col] + matrix_b[row][col] for col in range(len(matrix_a[row]))] | ||
for row in range(len(matrix_a)) | ||
] | ||
|
||
|
||
def matrix_subtraction(matrix_a: List, matrix_b: List): | ||
return [ | ||
[matrix_a[row][col] - matrix_b[row][col] for col in range(len(matrix_a[row]))] | ||
for row in range(len(matrix_a)) | ||
] | ||
|
||
|
||
def split_matrix(a: List,) -> Tuple[List, List, List, List]: | ||
""" | ||
Given an even length matrix, returns the top_left, top_right, bot_left, bot_right quadrant. | ||
>>> split_matrix([[4,3,2,4],[2,3,1,1],[6,5,4,3],[8,4,1,6]]) | ||
([[4, 3], [2, 3]], [[2, 4], [1, 1]], [[6, 5], [8, 4]], [[4, 3], [1, 6]]) | ||
>>> split_matrix([[4,3,2,4,4,3,2,4],[2,3,1,1,2,3,1,1],[6,5,4,3,6,5,4,3],[8,4,1,6,8,4,1,6],[4,3,2,4,4,3,2,4],[2,3,1,1,2,3,1,1],[6,5,4,3,6,5,4,3],[8,4,1,6,8,4,1,6]]) | ||
([[4, 3, 2, 4], [2, 3, 1, 1], [6, 5, 4, 3], [8, 4, 1, 6]], [[4, 3, 2, 4], [2, 3, 1, 1], [6, 5, 4, 3], [8, 4, 1, 6]], [[4, 3, 2, 4], [2, 3, 1, 1], [6, 5, 4, 3], [8, 4, 1, 6]], [[4, 3, 2, 4], [2, 3, 1, 1], [6, 5, 4, 3], [8, 4, 1, 6]]) | ||
""" | ||
if len(a) % 2 != 0 or len(a[0]) % 2 != 0: | ||
raise Exception("Odd matrices are not supported!") | ||
|
||
matrix_length = len(a) | ||
mid = matrix_length // 2 | ||
|
||
top_right = [[a[i][j] for j in range(mid, matrix_length)] for i in range(mid)] | ||
bot_right = [ | ||
[a[i][j] for j in range(mid, matrix_length)] for i in range(mid, matrix_length) | ||
] | ||
|
||
top_left = [[a[i][j] for j in range(mid)] for i in range(mid)] | ||
bot_left = [[a[i][j] for j in range(mid)] for i in range(mid, matrix_length)] | ||
|
||
return top_left, top_right, bot_left, bot_right | ||
|
||
|
||
def matrix_dimensions(matrix: List) -> Tuple[int, int]: | ||
return len(matrix), len(matrix[0]) | ||
|
||
|
||
def print_matrix(matrix: List) -> None: | ||
for i in range(len(matrix)): | ||
print(matrix[i]) | ||
|
||
|
||
def actual_strassen(matrix_a: List, matrix_b: List) -> List: | ||
""" | ||
Recursive function to calculate the product of two matrices, using the Strassen Algorithm. | ||
It only supports even length matrices. | ||
""" | ||
if matrix_dimensions(matrix_a) == (2, 2): | ||
return default_matrix_multiplication(matrix_a, matrix_b) | ||
|
||
a, b, c, d = split_matrix(matrix_a) | ||
e, f, g, h = split_matrix(matrix_b) | ||
|
||
t1 = actual_strassen(a, matrix_subtraction(f, h)) | ||
t2 = actual_strassen(matrix_addition(a, b), h) | ||
t3 = actual_strassen(matrix_addition(c, d), e) | ||
t4 = actual_strassen(d, matrix_subtraction(g, e)) | ||
t5 = actual_strassen(matrix_addition(a, d), matrix_addition(e, h)) | ||
t6 = actual_strassen(matrix_subtraction(b, d), matrix_addition(g, h)) | ||
t7 = actual_strassen(matrix_subtraction(a, c), matrix_addition(e, f)) | ||
|
||
top_left = matrix_addition(matrix_subtraction(matrix_addition(t5, t4), t2), t6) | ||
top_right = matrix_addition(t1, t2) | ||
bot_left = matrix_addition(t3, t4) | ||
bot_right = matrix_subtraction(matrix_subtraction(matrix_addition(t1, t5), t3), t7) | ||
|
||
# construct the new matrix from our 4 quadrants | ||
new_matrix = [] | ||
for i in range(len(top_right)): | ||
new_matrix.append(top_left[i] + top_right[i]) | ||
for i in range(len(bot_right)): | ||
new_matrix.append(bot_left[i] + bot_right[i]) | ||
return new_matrix | ||
|
||
|
||
def strassen(matrix1: List, matrix2: List) -> List: | ||
""" | ||
>>> strassen([[2,1,3],[3,4,6],[1,4,2],[7,6,7]], [[4,2,3,4],[2,1,1,1],[8,6,4,2]]) | ||
[[34, 23, 19, 15], [68, 46, 37, 28], [28, 18, 15, 12], [96, 62, 55, 48]] | ||
>>> strassen([[3,7,5,6,9],[1,5,3,7,8],[1,4,4,5,7]], [[2,4],[5,2],[1,7],[5,5],[7,8]]) | ||
[[139, 163], [121, 134], [100, 121]] | ||
""" | ||
if matrix_dimensions(matrix1)[1] != matrix_dimensions(matrix2)[0]: | ||
raise Exception( | ||
f"Unable to multiply these matrices, please check the dimensions. \nMatrix A:{matrix1} \nMatrix B:{matrix2}" | ||
) | ||
dimension1 = matrix_dimensions(matrix1) | ||
dimension2 = matrix_dimensions(matrix2) | ||
|
||
if dimension1[0] == dimension1[1] and dimension2[0] == dimension2[1]: | ||
return matrix1, matrix2 | ||
|
||
maximum = max(max(dimension1), max(dimension2)) | ||
maxim = int(math.pow(2, math.ceil(math.log2(maximum)))) | ||
new_matrix1 = matrix1 | ||
new_matrix2 = matrix2 | ||
|
||
# Adding zeros to the matrices so that the arrays dimensions are the same and also power of 2 | ||
for i in range(0, maxim): | ||
if i < dimension1[0]: | ||
for j in range(dimension1[1], maxim): | ||
new_matrix1[i].append(0) | ||
else: | ||
new_matrix1.append([0] * maxim) | ||
if i < dimension2[0]: | ||
for j in range(dimension2[1], maxim): | ||
new_matrix2[i].append(0) | ||
else: | ||
new_matrix2.append([0] * maxim) | ||
|
||
final_matrix = actual_strassen(new_matrix1, new_matrix2) | ||
|
||
# Removing the additional zeros | ||
for i in range(0, maxim): | ||
if i < dimension1[0]: | ||
for j in range(dimension2[1], maxim): | ||
final_matrix[i].pop() | ||
else: | ||
final_matrix.pop() | ||
return final_matrix | ||
|
||
|
||
if __name__ == "__main__": | ||
matrix1= [ | ||
[2, 3, 4, 5], | ||
[6, 4, 3, 1], | ||
[2, 3, 6, 7], | ||
[3, 1, 2, 4], | ||
[2, 3, 4, 5], | ||
[6, 4, 3, 1], | ||
[2, 3, 6, 7], | ||
[3, 1, 2, 4], | ||
[2, 3, 4, 5], | ||
[6, 2, 3, 1], | ||
] | ||
matrix2 = [[0, 2, 1, 1], [16, 2, 3, 3], [2, 2, 7, 7], [13, 11, 22, 4]] | ||
print(strassen(matrix1, matrix2)) |