Skip to content

Commit 9492e7a

Browse files
McDiccclauss
authored andcommitted
Created Sherman Morrison method (TheAlgorithms#1162)
* Created Sherman Morrison * Added docstring for class * Updated Sherman morrison 1. Added docstring tests 2. Tweaked __str__() using join 3. Added __repr__() 4. Changed index validation to be independent method * Applied cclauss's point 1. Reduced line length for __str__() 2. Removed parens for assert
1 parent d567a9e commit 9492e7a

File tree

1 file changed

+255
-0
lines changed

1 file changed

+255
-0
lines changed

‎matrix/sherman_morrison.py

+255
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
class Matrix:
2+
"""
3+
<class Matrix>
4+
Matrix structure.
5+
"""
6+
7+
def __init__(self, row: int, column: int, default_value: float = 0):
8+
"""
9+
<method Matrix.__init__>
10+
Initialize matrix with given size and default value.
11+
12+
Example:
13+
>>> a = Matrix(2, 3, 1)
14+
>>> a
15+
Matrix consist of 2 rows and 3 columns
16+
[1, 1, 1]
17+
[1, 1, 1]
18+
"""
19+
20+
self.row, self.column = row, column
21+
self.array = [[default_value for c in range(column)] for r in range(row)]
22+
23+
def __str__(self):
24+
"""
25+
<method Matrix.__str__>
26+
Return string representation of this matrix.
27+
"""
28+
29+
# Prefix
30+
s = "Matrix consist of %d rows and %d columns\n" % (self.row, self.column)
31+
32+
# Make string identifier
33+
max_element_length = 0
34+
for row_vector in self.array:
35+
for obj in row_vector:
36+
max_element_length = max(max_element_length, len(str(obj)))
37+
string_format_identifier = "%%%ds" % (max_element_length,)
38+
39+
# Make string and return
40+
def single_line(row_vector):
41+
nonlocal string_format_identifier
42+
line = "["
43+
line += ", ".join(string_format_identifier % (obj,) for obj in row_vector)
44+
line += "]"
45+
return line
46+
s += "\n".join(single_line(row_vector) for row_vector in self.array)
47+
return s
48+
49+
def __repr__(self): return str(self)
50+
51+
def validateIndices(self, loc: tuple):
52+
"""
53+
<method Matrix.validateIndices>
54+
Check if given indices are valid to pick element from matrix.
55+
56+
Example:
57+
>>> a = Matrix(2, 6, 0)
58+
>>> a.validateIndices((2, 7))
59+
False
60+
>>> a.validateIndices((0, 0))
61+
True
62+
"""
63+
if not(isinstance(loc, (list, tuple)) and len(loc) == 2): return False
64+
elif not(0 <= loc[0] < self.row and 0 <= loc[1] < self.column): return False
65+
else: return True
66+
67+
def __getitem__(self, loc: tuple):
68+
"""
69+
<method Matrix.__getitem__>
70+
Return array[row][column] where loc = (row, column).
71+
72+
Example:
73+
>>> a = Matrix(3, 2, 7)
74+
>>> a[1, 0]
75+
7
76+
"""
77+
assert self.validateIndices(loc)
78+
return self.array[loc[0]][loc[1]]
79+
80+
def __setitem__(self, loc: tuple, value: float):
81+
"""
82+
<method Matrix.__setitem__>
83+
Set array[row][column] = value where loc = (row, column).
84+
85+
Example:
86+
>>> a = Matrix(2, 3, 1)
87+
>>> a[1, 2] = 51
88+
>>> a
89+
Matrix consist of 2 rows and 3 columns
90+
[ 1, 1, 1]
91+
[ 1, 1, 51]
92+
"""
93+
assert self.validateIndices(loc)
94+
self.array[loc[0]][loc[1]] = value
95+
96+
def __add__(self, another):
97+
"""
98+
<method Matrix.__add__>
99+
Return self + another.
100+
101+
Example:
102+
>>> a = Matrix(2, 1, -4)
103+
>>> b = Matrix(2, 1, 3)
104+
>>> a+b
105+
Matrix consist of 2 rows and 1 columns
106+
[-1]
107+
[-1]
108+
"""
109+
110+
# Validation
111+
assert isinstance(another, Matrix)
112+
assert self.row == another.row and self.column == another.column
113+
114+
# Add
115+
result = Matrix(self.row, self.column)
116+
for r in range(self.row):
117+
for c in range(self.column):
118+
result[r,c] = self[r,c] + another[r,c]
119+
return result
120+
121+
def __neg__(self):
122+
"""
123+
<method Matrix.__neg__>
124+
Return -self.
125+
126+
Example:
127+
>>> a = Matrix(2, 2, 3)
128+
>>> a[0, 1] = a[1, 0] = -2
129+
>>> -a
130+
Matrix consist of 2 rows and 2 columns
131+
[-3, 2]
132+
[ 2, -3]
133+
"""
134+
135+
result = Matrix(self.row, self.column)
136+
for r in range(self.row):
137+
for c in range(self.column):
138+
result[r,c] = -self[r,c]
139+
return result
140+
141+
def __sub__(self, another): return self + (-another)
142+
143+
def __mul__(self, another):
144+
"""
145+
<method Matrix.__mul__>
146+
Return self * another.
147+
148+
Example:
149+
>>> a = Matrix(2, 3, 1)
150+
>>> a[0,2] = a[1,2] = 3
151+
>>> a * -2
152+
Matrix consist of 2 rows and 3 columns
153+
[-2, -2, -6]
154+
[-2, -2, -6]
155+
"""
156+
157+
if isinstance(another, (int, float)): # Scalar multiplication
158+
result = Matrix(self.row, self.column)
159+
for r in range(self.row):
160+
for c in range(self.column):
161+
result[r,c] = self[r,c] * another
162+
return result
163+
elif isinstance(another, Matrix): # Matrix multiplication
164+
assert(self.column == another.row)
165+
result = Matrix(self.row, another.column)
166+
for r in range(self.row):
167+
for c in range(another.column):
168+
for i in range(self.column):
169+
result[r,c] += self[r,i] * another[i,c]
170+
return result
171+
else: raise TypeError("Unsupported type given for another (%s)" % (type(another),))
172+
173+
def transpose(self):
174+
"""
175+
<method Matrix.transpose>
176+
Return self^T.
177+
178+
Example:
179+
>>> a = Matrix(2, 3)
180+
>>> for r in range(2):
181+
... for c in range(3):
182+
... a[r,c] = r*c
183+
...
184+
>>> a.transpose()
185+
Matrix consist of 3 rows and 2 columns
186+
[0, 0]
187+
[0, 1]
188+
[0, 2]
189+
"""
190+
191+
result = Matrix(self.column, self.row)
192+
for r in range(self.row):
193+
for c in range(self.column):
194+
result[c,r] = self[r,c]
195+
return result
196+
197+
def ShermanMorrison(self, u, v):
198+
"""
199+
<method Matrix.ShermanMorrison>
200+
Apply Sherman-Morrison formula in O(n^2).
201+
To learn this formula, please look this: https://en.wikipedia.org/wiki/Sherman%E2%80%93Morrison_formula
202+
This method returns (A + uv^T)^(-1) where A^(-1) is self. Returns None if it's impossible to calculate.
203+
Warning: This method doesn't check if self is invertible.
204+
Make sure self is invertible before execute this method.
205+
206+
Example:
207+
>>> ainv = Matrix(3, 3, 0)
208+
>>> for i in range(3): ainv[i,i] = 1
209+
...
210+
>>> u = Matrix(3, 1, 0)
211+
>>> u[0,0], u[1,0], u[2,0] = 1, 2, -3
212+
>>> v = Matrix(3, 1, 0)
213+
>>> v[0,0], v[1,0], v[2,0] = 4, -2, 5
214+
>>> ainv.ShermanMorrison(u, v)
215+
Matrix consist of 3 rows and 3 columns
216+
[ 1.2857142857142856, -0.14285714285714285, 0.3571428571428571]
217+
[ 0.5714285714285714, 0.7142857142857143, 0.7142857142857142]
218+
[ -0.8571428571428571, 0.42857142857142855, -0.0714285714285714]
219+
"""
220+
221+
# Size validation
222+
assert isinstance(u, Matrix) and isinstance(v, Matrix)
223+
assert self.row == self.column == u.row == v.row # u, v should be column vector
224+
assert u.column == v.column == 1 # u, v should be column vector
225+
226+
# Calculate
227+
vT = v.transpose()
228+
numerator_factor = (vT * self * u)[0, 0] + 1
229+
if numerator_factor == 0: return None # It's not invertable
230+
return self - ((self * u) * (vT * self) * (1.0 / numerator_factor))
231+
232+
# Testing
233+
if __name__ == "__main__":
234+
235+
def test1():
236+
# a^(-1)
237+
ainv = Matrix(3, 3, 0)
238+
for i in range(3): ainv[i,i] = 1
239+
print("a^(-1) is %s" % (ainv,))
240+
# u, v
241+
u = Matrix(3, 1, 0)
242+
u[0,0], u[1,0], u[2,0] = 1, 2, -3
243+
v = Matrix(3, 1, 0)
244+
v[0,0], v[1,0], v[2,0] = 4, -2, 5
245+
print("u is %s" % (u,))
246+
print("v is %s" % (v,))
247+
print("uv^T is %s" % (u * v.transpose()))
248+
# Sherman Morrison
249+
print("(a + uv^T)^(-1) is %s" % (ainv.ShermanMorrison(u, v),))
250+
251+
def test2():
252+
import doctest
253+
doctest.testmod()
254+
255+
test2()

0 commit comments

Comments
 (0)