A simple class in Python representing a Matrix with basic operations, operator overloading and class factory methods to make Matrices from different sources.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 | import random
import operator
import sys
import unittest
__version__ = "0.3"
class MatrixError(Exception):
""" An exception class for Matrix """
pass
class Matrix(object):
""" A simple Python matrix class with
basic operations and operator overloading """
def __init__(self, m, n, init=True):
if init:
self.rows = [[0]*n for x in range(m)]
else:
self.rows = []
self.m = m
self.n = n
def __getitem__(self, idx):
return self.rows[idx]
def __setitem__(self, idx, item):
self.rows[idx] = item
def __str__(self):
s='\n'.join([' '.join([str(item) for item in row]) for row in self.rows])
return s + '\n'
def __repr__(self):
s=str(self.rows)
rank = str(self.getRank())
rep="Matrix: \"%s\", rank: \"%s\"" % (s,rank)
return rep
def reset(self):
""" Reset the matrix data """
self.rows = [[] for x in range(self.m)]
def transpose(self):
""" Transpose the matrix. Changes the current matrix """
self.m, self.n = self.n, self.m
self.rows = [list(item) for item in zip(*self.rows)]
def getTranspose(self):
""" Return a transpose of the matrix without
modifying the matrix itself """
m, n = self.n, self.m
mat = Matrix(m, n)
mat.rows = [list(item) for item in zip(*self.rows)]
return mat
def getRank(self):
return (self.m, self.n)
def __eq__(self, mat):
""" Test equality """
return (mat.rows == self.rows)
def __add__(self, mat):
""" Add a matrix to this matrix and
return the new matrix. Doesn't modify
the current matrix """
if self.getRank() != mat.getRank():
raise MatrixError, "Trying to add matrixes of varying rank!"
ret = Matrix(self.m, self.n)
for x in range(self.m):
row = [sum(item) for item in zip(self.rows[x], mat[x])]
ret[x] = row
return ret
def __sub__(self, mat):
""" Subtract a matrix from this matrix and
return the new matrix. Doesn't modify
the current matrix """
if self.getRank() != mat.getRank():
raise MatrixError, "Trying to add matrixes of varying rank!"
ret = Matrix(self.m, self.n)
for x in range(self.m):
row = [item[0]-item[1] for item in zip(self.rows[x], mat[x])]
ret[x] = row
return ret
def __mul__(self, mat):
""" Multiple a matrix with this matrix and
return the new matrix. Doesn't modify
the current matrix """
matm, matn = mat.getRank()
if (self.n != matm):
raise MatrixError, "Matrices cannot be multipled!"
mat_t = mat.getTranspose()
mulmat = Matrix(self.m, matn)
for x in range(self.m):
for y in range(mat_t.m):
mulmat[x][y] = sum([item[0]*item[1] for item in zip(self.rows[x], mat_t[y])])
return mulmat
def __iadd__(self, mat):
""" Add a matrix to this matrix.
This modifies the current matrix """
# Calls __add__
tempmat = self + mat
self.rows = tempmat.rows[:]
return self
def __isub__(self, mat):
""" Add a matrix to this matrix.
This modifies the current matrix """
# Calls __sub__
tempmat = self - mat
self.rows = tempmat.rows[:]
return self
def __imul__(self, mat):
""" Add a matrix to this matrix.
This modifies the current matrix """
# Possibly not a proper operation
# since this changes the current matrix
# rank as well...
# Calls __mul__
tempmat = self * mat
self.rows = tempmat.rows[:]
self.m, self.n = tempmat.getRank()
return self
def save(self, filename):
open(filename, 'w').write(str(self))
@classmethod
def _makeMatrix(cls, rows):
m = len(rows)
n = len(rows[0])
# Validity check
if any([len(row) != n for row in rows[1:]]):
raise MatrixError, "inconsistent row length"
mat = Matrix(m,n, init=False)
mat.rows = rows
return mat
@classmethod
def makeRandom(cls, m, n, low=0, high=10):
""" Make a random matrix with elements in range (low-high) """
obj = Matrix(m, n, init=False)
for x in range(m):
obj.rows.append([random.randrange(low, high) for i in range(obj.n)])
return obj
@classmethod
def makeZero(cls, m, n):
""" Make a zero-matrix of rank (mxn) """
rows = [[0]*n for x in range(m)]
return cls.fromList(rows)
@classmethod
def makeId(cls, m):
""" Make identity matrix of rank (mxm) """
rows = [[0]*m for x in range(m)]
idx = 0
for row in rows:
row[idx] = 1
idx += 1
return cls.fromList(rows)
@classmethod
def readStdin(cls):
""" Read a matrix from standard input """
print 'Enter matrix row by row. Type "q" to quit'
rows = []
while True:
line = sys.stdin.readline().strip()
if line=='q': break
row = [int(x) for x in line.split()]
rows.append(row)
return cls._makeMatrix(rows)
@classmethod
def readGrid(cls, fname):
""" Read a matrix from a file """
rows = []
for line in open(fname).readlines():
row = [int(x) for x in line.split()]
rows.append(row)
return cls._makeMatrix(rows)
@classmethod
def fromList(cls, listoflists):
""" Create a matrix by directly passing a list
of lists """
# E.g: Matrix.fromList([[1 2 3], [4,5,6], [7,8,9]])
rows = listoflists[:]
return cls._makeMatrix(rows)
class MatrixTests(unittest.TestCase):
def testAdd(self):
m1 = Matrix.fromList([[1, 2, 3], [4, 5, 6]])
m2 = Matrix.fromList([[7, 8, 9], [10, 11, 12]])
m3 = m1 + m2
self.assertTrue(m3 == Matrix.fromList([[8, 10, 12], [14,16,18]]))
def testSub(self):
m1 = Matrix.fromList([[1, 2, 3], [4, 5, 6]])
m2 = Matrix.fromList([[7, 8, 9], [10, 11, 12]])
m3 = m2 - m1
self.assertTrue(m3 == Matrix.fromList([[6, 6, 6], [6, 6, 6]]))
def testMul(self):
m1 = Matrix.fromList([[1, 2, 3], [4, 5, 6]])
m2 = Matrix.fromList([[7, 8], [10, 11], [12, 13]])
self.assertTrue(m1 * m2 == Matrix.fromList([[63, 69], [150, 165]]))
self.assertTrue(m2*m1 == Matrix.fromList([[39, 54, 69], [54, 75, 96], [64, 89, 114]]))
def testTranspose(self):
m1 = Matrix.makeRandom(25, 30)
zerom = Matrix.makeZero(25, 30)
m2 = m1 + zerom
m1.transpose()
m1.transpose()
self.assertTrue(m2 == m1)
# Also test getTranspose
m2 = m1.getTranspose()
r2 = m2.getRank()
self.assertTrue(r2==(30,25))
m2.transpose()
self.assertTrue(m2 == m1)
def testId(self):
m1 = Matrix.makeId(10)
m2 = Matrix.makeRandom(4, 10)
m3 = m2*m1
self.assertTrue(m3 == m2)
if __name__ == "__main__":
unittest.main()
|
There are quite a few recipes here which provide Matrix classes but I couldn't come across anything that provides a very simple Matrix class without too much complexity.
My requirements where a class that performs most of the operations with rows (no double indexing as much as possible), does transpose very optimally even for large matrices, provides operator overloading to naturally perform operations and a few factory methods to create matrices all in one class with simple code.
The end result is the above code. This can be used for any quick code that needs Matrices without needing to import another library.
Sample usage.
>>> from matrix import Matrix
>>> m = Matrix.makeRandom(3,3)
>>> print m
0 5 8
5 0 3
7 7 5
>>> m2 = Matrix.makeRandom(3,3)
>>> print m2
2 6 0
7 1 4
1 7 6
>>> print m + m2
2 11 8
12 1 7
8 14 11
>>> print m - m2
-2 -1 8
-2 -1 -1
6 0 -1
>>> m3 = Matrix.makeRandom(3,2)
>>> print m3
4 4
5 2
5 9
>>> print m * m3
65 82
35 47
88 87
>>> m4 = Matrix.makeRandom(3,3)
>>> print m4
7 0 4
2 0 9
3 4 7
>>> m += m4
>>> print m
7 5 12
7 0 12
10 11 12
>>> m5 = Matrix.makeRandom(3,3)
>>> print m5
3 8 0
3 0 9
9 9 4
>>> m -= m5
>>> print m
4 -3 12
4 0 3
1 2 8
>>> m *= m3
>>> print m
61 118
31 43
54 80
>>> m6 = Matrix.readStdin()
Enter matrix row by row. Type "q" to quit
1 2 3
4 5 6
q
>>> print m6
1 2 3
4 5 6
>>> m6.transpose()
>>> print m6
1 4
2 5
3 6
>>> open('grid.txt','w').write(str(m6))
>>> m7 = Matrix.readGrid('grid.txt')
>>> print (m7 == m6)
True
>>> m8 = Matrix.makeRandom(25, 35)
>>> m8.transpose()
>>> print m8.getRank()
(35, 25)
Version 2.0 - Added unit-tests, repr, factory function to make Matrix from lists. Version 3.0 - Added makeId, fixed stupid prev transpose method to use zip()
Would be even more useful if it had some methods for basic matrix transformations were added.
having trouble understanding the value of having init=True as a case in your __init__ definition. Can you give an instance when you would want init=True vs another where you would want init=False?