Skip to content

Commit e29467d

Browse files
authored
Merge pull request #7 from StephenGemin/update_matrix_ops
Update matrix ops Within the matrix folder: 1. Removed init.py 2. Added pytests for matrix operations as it was difficult to see if things were working correctly just running it through print statements on the main file. There were edge cases that the algorithms did not account for. Made the following changes to matrix_operation.py 1. added matrix subtraction to matrix_operation.py 2. added matrix size checks for addition and subtraction as there were previously no checks 3. fixed typo in matrix multiplication loop that was in Pull Request #898 on TheAlgorithms/Python 4. PEP8 changes
2 parents a6e7ff9 + 85bc484 commit e29467d

File tree

5 files changed

+160
-32
lines changed

5 files changed

+160
-32
lines changed

matrix/__init__.py

-1
This file was deleted.

matrix/matrix_operation.py

+75-30
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,121 @@
11
from __future__ import print_function
22

3+
34
def add(matrix_a, matrix_b):
4-
rows = len(matrix_a)
5-
columns = len(matrix_a[0])
5+
try:
6+
rows = [len(matrix_a), len(matrix_b)]
7+
cols = [len(matrix_a[0]), len(matrix_b[0])]
8+
except TypeError:
9+
raise TypeError("Cannot input an integer value, it must be a matrix")
10+
11+
if rows[0] != rows[1] & cols[0] != cols[1]:
12+
raise ValueError(f"operands could not be broadcast together with shapes "
13+
f"({rows[0],cols[0]}), ({rows[1], cols[1]})")
14+
615
matrix_c = []
7-
for i in range(rows):
16+
for i in range(rows[0]):
817
list_1 = []
9-
for j in range(columns):
18+
for j in range(cols[0]):
1019
val = matrix_a[i][j] + matrix_b[i][j]
1120
list_1.append(val)
1221
matrix_c.append(list_1)
1322
return matrix_c
1423

15-
def scalarMultiply(matrix , n):
24+
25+
def subtract(matrix_a, matrix_b):
26+
try:
27+
rows = [len(matrix_a), len(matrix_b)]
28+
cols = [len(matrix_a[0]), len(matrix_b)]
29+
except TypeError:
30+
raise TypeError("Cannot input an integer value, it must be a matrix")
31+
32+
if rows[0] != rows[1] & cols[0] != cols[1]:
33+
raise ValueError(f"operands could not be broadcast together with shapes "
34+
f"({rows[0], cols[0]}), ({rows[1], cols[1]})")
35+
36+
matrix_c = []
37+
for i in range(rows[0]):
38+
list_1 = []
39+
for j in range(cols[0]):
40+
val = matrix_a[i][j] - matrix_b[i][j]
41+
list_1.append(val)
42+
matrix_c.append(list_1)
43+
return matrix_c
44+
45+
46+
def scalar_multiply(matrix, n):
1647
return [[x * n for x in row] for row in matrix]
1748

49+
1850
def multiply(matrix_a, matrix_b):
1951
matrix_c = []
20-
n = len(matrix_a)
21-
for i in range(n):
52+
num_rows_a = len(matrix_a)
53+
num_cols_a = len(matrix_a[0])
54+
num_rows_b = len(matrix_b)
55+
num_cols_b = len(matrix_b[0])
56+
57+
if num_cols_a != num_rows_b:
58+
raise ValueError(f'Cannot multiply matrix of dimensions ({num_rows_a},{num_cols_a}) '
59+
f'and ({num_rows_b},{num_cols_b})')
60+
61+
for i in range(num_rows_a):
2262
list_1 = []
23-
for j in range(n):
63+
for j in range(num_cols_b):
2464
val = 0
25-
for k in range(n):
65+
for k in range(num_cols_b):
2666
val = val + matrix_a[i][k] * matrix_b[k][j]
2767
list_1.append(val)
2868
matrix_c.append(list_1)
2969
return matrix_c
3070

71+
3172
def identity(n):
3273
return [[int(row == column) for column in range(n)] for row in range(n)]
3374

75+
3476
def transpose(matrix):
35-
return map(list , zip(*matrix))
77+
return map(list, zip(*matrix))
78+
3679

3780
def minor(matrix, row, column):
3881
minor = matrix[:row] + matrix[row + 1:]
3982
minor = [row[:column] + row[column + 1:] for row in minor]
4083
return minor
4184

85+
4286
def determinant(matrix):
43-
if len(matrix) == 1: return matrix[0][0]
87+
if len(matrix) == 1:
88+
return matrix[0][0]
4489

4590
res = 0
4691
for x in range(len(matrix)):
47-
res += matrix[0][x] * determinant(minor(matrix , 0 , x)) * (-1) ** x
92+
res += matrix[0][x] * determinant(minor(matrix, 0, x)) * (-1) ** x
4893
return res
4994

95+
5096
def inverse(matrix):
5197
det = determinant(matrix)
52-
if det == 0: return None
98+
if det == 0:
99+
return None
53100

54-
matrixMinor = [[] for _ in range(len(matrix))]
101+
matrix_minor = [[] for _ in range(len(matrix))]
55102
for i in range(len(matrix)):
56103
for j in range(len(matrix)):
57-
matrixMinor[i].append(determinant(minor(matrix , i , j)))
104+
matrix_minor[i].append(determinant(minor(matrix, i, j)))
58105

59-
cofactors = [[x * (-1) ** (row + col) for col, x in enumerate(matrixMinor[row])] for row in range(len(matrix))]
106+
cofactors = [[x * (-1) ** (row + col) for col, x in enumerate(matrix_minor[row])] for row in range(len(matrix))]
60107
adjugate = transpose(cofactors)
61-
return scalarMultiply(adjugate , 1/det)
62-
63-
def main():
64-
matrix_a = [[12, 10], [3, 9]]
65-
matrix_b = [[3, 4], [7, 4]]
66-
matrix_c = [[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34], [41, 42, 43, 44]]
67-
matrix_d = [[3, 0, 2], [2, 0, -2], [0, 1, 1]]
68-
print('Add Operation, %s + %s = %s \n' %(matrix_a, matrix_b, (add(matrix_a, matrix_b))))
69-
print('Multiply Operation, %s * %s = %s \n' %(matrix_a, matrix_b, multiply(matrix_a, matrix_b)))
70-
print('Identity: %s \n' %identity(5))
71-
print('Minor of %s = %s \n' %(matrix_c, minor(matrix_c , 1 , 2)))
72-
print('Determinant of %s = %s \n' %(matrix_b, determinant(matrix_b)))
73-
print('Inverse of %s = %s\n'%(matrix_d, inverse(matrix_d)))
108+
return scalar_multiply(adjugate, 1/det)
109+
74110

75111
if __name__ == '__main__':
76-
main()
112+
mat_a = [[12, 10], [3, 9]]
113+
mat_b = [[3, 4], [7, 4]]
114+
mat_c = [[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34], [41, 42, 43, 44]]
115+
mat_d = [[3, 0, 2], [2, 0, -2], [0, 1, 1]]
116+
print('Add Operation, %s + %s = %s \n' %(mat_a, mat_b, (add(mat_a, mat_b))))
117+
print('Multiply Operation, %s * %s = %s \n' %(mat_a, mat_b, multiply(mat_a, mat_b)))
118+
print('Identity: %s \n' %identity(5))
119+
print('Minor of %s = %s \n' %(mat_c, minor(mat_c , 1 , 2)))
120+
print('Determinant of %s = %s \n' %(mat_b, determinant(mat_b)))
121+
print('Inverse of %s = %s\n'%(mat_d, inverse(mat_d)))

matrix/tests/pytest.ini

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# content of pytest.ini
2+
[pytest]
3+
markers =
4+
mat_ops: mark a test as a matrix operations test

matrix/tests/test_matrix-operations.py

-1
This file was deleted.
+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"""
2+
Testing here assumes that numpy is ALWAYS correct!!!!
3+
4+
If running from PyCharm you can place the following line in "Additional Arguments" for the pytest run configuration
5+
-vv -m mat_ops -p no:cacheprovider
6+
"""
7+
8+
# standard libraries
9+
import numpy as np
10+
import pytest
11+
12+
# Custom/local libraries
13+
from matrix import matrix_operation as matop
14+
15+
mat_a = [[12, 10], [3, 9]]
16+
mat_b = [[3, 4], [7, 4]]
17+
mat_c = [[3, 0, 2], [2, 0, -2], [0, 1, 1]]
18+
mat_d = [[3, 0, 2], [2, 0, -2], [0, 1, 1]]
19+
mat_e = [[3, 0, 2], [2, 0, -2], [0, 1, 1], [2, 0, -2]]
20+
mat_f = [1]
21+
mat_h = [2]
22+
23+
24+
@pytest.mark.mat_ops
25+
@pytest.mark.parametrize(('mat1', 'mat2'), [(mat_a, mat_b), (mat_c, mat_d), (mat_d, mat_e),
26+
(mat_f, mat_h)])
27+
def test_addition(mat1, mat2):
28+
# Catch when user enters a single integer value rather than a mat
29+
# Check for known errors
30+
if (np.array(mat1)).shape < (2, 2) or (np.array(mat2)).shape < (2, 2):
31+
with pytest.raises(TypeError):
32+
assert matop.add(mat1, mat2)
33+
elif (np.array(mat1)).shape == (np.array(mat2)).shape:
34+
act = (np.array(mat1) + np.array(mat2)).tolist()
35+
theo = matop.add(mat1, mat2)
36+
assert theo == act
37+
else:
38+
# matrices have different dimensions
39+
# Check for known errors
40+
with pytest.raises(ValueError):
41+
assert matop.add(mat1, mat2)
42+
43+
44+
@pytest.mark.mat_ops
45+
@pytest.mark.parametrize(('mat1', 'mat2'), [(mat_a, mat_b), (mat_c, mat_d), (mat_d, mat_e),
46+
(mat_f, mat_h)])
47+
def test_subtraction(mat1, mat2):
48+
# Catch when user enters a single integer value rather than a mat
49+
# Check for known errors
50+
if (np.array(mat1)).shape < (2, 2) or (np.array(mat2)).shape < (2, 2):
51+
with pytest.raises(TypeError):
52+
assert matop.add(mat1, mat2)
53+
elif (np.array(mat1)).shape == (np.array(mat2)).shape:
54+
act = (np.array(mat1) - np.array(mat2)).tolist()
55+
theo = matop.subtract(mat1, mat2)
56+
assert theo == act
57+
else:
58+
# matrices have different dimensions
59+
# Check for known errors
60+
with pytest.raises(ValueError):
61+
assert matop.subtract(mat1, mat2)
62+
63+
64+
@pytest.mark.mat_ops
65+
@pytest.mark.parametrize(('mat1', 'mat2'), [(mat_a, mat_b), (mat_c, mat_d), (mat_d, mat_e),
66+
(mat_f, mat_h)])
67+
def test_multiplication(mat1, mat2):
68+
# Catch when user enters a single integer value rather than a mat
69+
# Check for known errors
70+
if (np.array(mat1)).shape < (2, 2) or (np.array(mat2)).shape < (2, 2):
71+
with pytest.raises(TypeError):
72+
assert matop.add(mat1, mat2)
73+
elif (np.array(mat1)).shape == (np.array(mat2)).shape:
74+
act = (np.matmul(mat1, mat2)).tolist()
75+
theo = matop.multiply(mat1, mat2)
76+
assert theo == act
77+
else:
78+
# matrices have different dimensions
79+
# Check for known errors
80+
with pytest.raises(ValueError):
81+
assert matop.subtract(mat1, mat2)

0 commit comments

Comments
 (0)