Creating a Matrix Class in Swift

Michael Michailidis
5 min readNov 28, 2016

(Build for Swift 3.0 on GitHub)

Swift exposes some sought-after features that graphics programmers missed from languages like C++, where most of the game engines are programmed in. Game engines of course use matrices heavily and so we come full circle to Swift.

A matrix is an object that has tabular data, so in terms of properties we have two main things: data, and dimensions, as in width and height. We are going to start by creating a new class that implements CustomStringConvertible. This is a public protocol that allows for an object to be.

class Matrix: CustomStringConvertible {

internal var data:Array<Double>

var rows: Int
var columns: Int

init(_ data:Array<Double>, rows:Int, columns:Int) {
self.data = data
self.rows = rows
self.columns = columns
}

init(rows:Int, columns:Int) {
self.data = [Double](repeating: 0.0, count: rows*columns)
self.rows = rows
self.columns = columns
}

}

Accessing Data

The array we are passing is one dimensional and we could I suppose have a 2d array which would include the rows and columns properties as part of its structure. While coding this however I found that it is easier to just pass a standard array and indicate the shape of the matrix separately. It is also the approach that well written libraries like numpy are currently using. Another initialiser will create a matrix full of 0.0 values.

subscript(row: Int, col: Int) -> Double {
get {
return data[(row * columns) + col]
}

set {
self.data[(row * columns) + col] = newValue
}
}

Holding it’s data in a 1D array means we need to calculate the index manually, but this is a small price to pay as we only need to do it once. After that we can access the data as:

let m = Matrix([0.5, 3.0] rows:1, columns:2) // create
let v = m[0,1] // v = 0.3 (2nd element of the 1rst row)

Printing

Now we definitely want to see the data printed out on a terminal at any given moment, and we also have the tools to do it!

override var description: String {
var dsc = ""
for row in 0..<rows {
for col in 0..<columns {
let s = String(self[row,col])
dsc += s + " "
}
dsc += "\n"
}
return dsc
}

Here we implemented the description property of the CustomStringConvertible which is called every time an object is being printed:

let m = Matrix([0.5, 3.0] rows:1, columns:2) // create
print("My Matrix is: \(m)")

Copying

One more utility function should be that of copying.

func copy(with zone: NSZone? = nil) -> Matrix {
let cp = Matrix(self.data, rows:self.rows, columns:self.columns)
return cp
}

Operations

Now we get to the meat of things! Since matrices are mathematical object we would like to use them inside expressions, like numbers. So we are going to overload standard operators like + and * to allow for this kind of manipulation.

Addition

func +(left: Matrix, right: Matrix) -> Matrix {

precondition(left.rows == right.rows && left.columns == right.columns)
let m = Matrix(left.data, rows: left.rows, columns: left.columns)
for row in 0..<left.rows {
for col in 0..<left.columns {
m[row,col] += right[row,col]
}
}
return m
}

As good developers we are also writing tests for our project. To test addition we write:

func testAddition() {
let sum = Matrix([2, 2], rows:1, columns:2) + Matrix([1, 1], rows:1, columns:2)
XCTAssert(sum == Matrix([3, 3], rows:1, columns:2), "1x1 Matrix wrong")
}

We can do the same for subtraction and multiplication with a scalar value. All we need to do is change += into -= and *=.

Equality

You might remember overriding the isEqual: function in objective-c. Here it gets much more intuitive as we can also use the == operator for custom types.

func ==(left:Matrix, right:Matrix) -> Bool {
if left.rows != right.rows {
return false
}
if left.columns != right.columns {
return false
}
for i in 0..<left.rows {
for j in 0..<left.columns {
if left[i,j] != right[i,j] {
return false
}
}
}
return true
}

For 2 matrices to be equal, all elements must be equal one by one.

Transposition

A special operation we can perform on matrices is transposition. This will essentially flip the matrix so that its rows become columns and vica versa. For this we will introduce an new operand. One that follows a matrix much like its mathematical notation does. Here I chose the caret ‘^’ character, but any one would do.

postfix operator ^
postfix func ^(m:Matrix) -> Matrix {
let t = Matrix(rows:m.columns, columns:m.rows)
for row in 0..<m.rows {
for col in 0..<m.columns {
t[col,row] = m[row,col]
}
}
return t
}

We test it with:

func testTransposition() {
var trans = Matrix([1, 2, 3, 4, 5, 6], rows:3, columns:2)
XCTAssert(trans^ == Matrix([1, 3, 5, 2, 4, 6], rows:2, columns:3), "Matrix did not transpose correctly")
}

Multiplication

Now for the tricky one. Matrix multiplication. We saw how this works by the example of the 2 populations above.

func *(left:Matrix, right:Matrix) -> Matrix {

var lcp = left.copy()
var rcp = right.copy()

if (lcp.rows == 1 && rcp.rows == 1) && (lcp.columns == rcp.columns) { // exception for single row matrices (inspired by numpy)
rcp = rcp^
}
else if (lcp.columns == 1 && rcp.columns == 1) && (lcp.rows == rcp.rows) { // exception for single row matrices (inspired by numpy)
lcp = lcp^
}

precondition(lcp.columns == rcp.rows, "Matrices cannot be multipied")

let dot = Matrix(rows:lcp.rows, columns:rcp.columns)

for i in 0..<lcp.rows {
for j in 0..<rcp.columns {
let a = lcp.row(index: i) ** rcp.col(index: j)
dot[i,j] = a
}
}
return dot
}

To multiply to matrices, the rows of the first must equal the columns of the second. In the code above however we spare some cases on behalf of the user by transposing a matrix that has a single row or a single column. My rational is that many times I will use Matrices as arrays, for the reason that I might want them to interact with matrices later.

You will notice a new operator in use **. This is my version of the inner product of two vectors. Multiply all elements of an array (vector) with another array of equal size and sum the results into a single value.

infix operator **
func **(left:[Double], right:[Double]) -> Double {
var d : Double = 0
for i in 0..<left.count {
d += left[i] * right[i]
}
return d
}

--

--

Michael Michailidis

I am a software engineer working in Node.js + iOS with an eye for blockchain applications!