Skip to content

Instantly share code, notes, and snippets.

@JadenGeller
Last active August 10, 2023 10:03
Show Gist options
  • Star 46 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save JadenGeller/8c758cbb218a9c4615dd to your computer and use it in GitHub Desktop.
Save JadenGeller/8c758cbb218a9c4615dd to your computer and use it in GitHub Desktop.
Matrices in Swift
// Numerical matrix examples
let x: Matrix = [[10, 9, 8], [3, 2, 1]]
let y: Matrix = [[1, 2, 3], [4, 5, 6]]
let z: Matrix = [[1, 2], [3, 4], [5, 6]]
x + y // [[11, 11, 11], [7, 7, 7]]
x * y // [[10, 18, 24], [12, 10, 6]]
2 * x // [[20, 18, 16], [6, 4, 2]]
y ** z // [[22, 28], [49, 64]]
z - x.transpose // [[-9, -1], [-6, 2], [-3, 5]]
y.map({ $0 * $0 }) // [[1, 4, 9], [16, 25, 36]]
println(y ** identityMatrix(y.columnCount)) // -> [[1, 2, 3], [4, 5, 6]]
let d: Matrix = [[-2, 2, -3], [-1, 1, 3], [2, 0, -1]]
println(determinant(d)) // -> 18
let e: Matrix = [[1, 2, 3], [0, 4, 5], [1, 0, 6]]
println(cofactor(e)) // -> [[24, 5, -4], [-12, -3, 2], [2, -5, -4]]
let f: Matrix<Double> = [[4, 3], [3, 2]]
println(inverse(f)) // -> [[-2.0, 3.0], [3.0, 4.0]]
var a: Matrix = [[1.1, 1.2, 1.3, 1.4, 1.5]]
a.append(column: [1.6])
a.append(row: [2.1, 2.2, 2.3, 2.4, 2.5, 2.6])
println(a) // -> [[1.1, 1.2, 1.3, 1.4, 1.5, 1.6], [2.1, 2.2, 2.3, 2.4, 2.5, 2.6]]
// We can also work with matricies of any other objects
var b: Matrix = [["hi", "hello"], ["bye", "goodbye"]]
println(b[0, 0]) // -> hi
println(b.rows[0]) // -> [hi, hello]
for (x, row, column) in b {
println("\(x) is at (\(row),\(column))") // -> hi is at (0,0)
} // hello is at (0,1)
// bye is at (1,0)
// goodbye is at (1,1)
b.removeColumn(atIndex: 0)
println(b) // -> [[hello], [goodbye]]
println(b.transpose) // -> [[hello, goodbye]]
println(b.flattened) // -> [hello, goodbye]
// Newer version here: https://github.com/JadenGeller/Dimensional
struct Matrix<T> : CustomStringConvertible, SequenceType, ArrayLiteralConvertible {
var backing: [[T]]
init(rows: Int, columns: Int, repeatedValue: T){
self.init(backing: [[T]](count: rows, repeatedValue: [T](count: columns, repeatedValue: repeatedValue)))
}
private init(backing: [[T]]){
self.backing = backing
}
init(arrayLiteral elements: [T]...) {
if let count = elements.first?.count {
for row in elements {
if row.count != count {
fatalError("Matrix must have same number of rows and columns")
}
}
}
self.init(backing: elements)
}
subscript(row: Int, column: Int) -> T {
get {
return backing[row][column]
}
set {
backing[row][column] = newValue
}
}
var dimensions: (rows: Int, columns: Int) {
get {
return (backing.count, backing.first?.count ?? 0)
}
}
var rowCount: Int {
get {
return backing.count
}
}
var columnCount: Int {
get {
return backing.first?.count ?? 0
}
}
var count: Int {
get {
return rowCount * columnCount
}
}
var rows: [[T]] {
get {
return backing
}
}
var columns: [[T]] {
get {
return backing.reduce([[T]](count: columnCount, repeatedValue: [T]()), combine: {
(var arr, row) in
for (i,x) in row.enumerate() {
arr[i].append(x)
}
return arr
})
}
}
var transpose: Matrix {
get {
return Matrix(backing: columns)
}
}
var flattened: [T] {
return backing.reduce([T](), combine: { arr, row in arr + row })
}
func reduce<U>(initial: U, @noescape combine: (U, T) -> U) -> U {
return flattened.reduce(initial, combine: combine)
}
var isEmpty: Bool {
return count == 0
}
var isSquare: Bool {
return rowCount == columnCount
}
var isColumnVector: Bool {
return rowCount == 1
}
var isRowVector: Bool {
return columnCount == 1
}
var description: String {
return backing.description
}
mutating func append(row row: [T]){
insert(row: row, atIndex: self.rowCount)
}
mutating func append(column column: [T]){
insert(column: column, atIndex: self.columnCount)
}
mutating func insert(row row: [T], atIndex index: Int){
if !isEmpty {
assert(row.count == self.columnCount, "Row count does not match dimensions of matrix")
}
backing.insert(row, atIndex: index)
}
mutating func insert(column column: [T], atIndex index: Int){
if !isEmpty {
assert(column.count == self.rowCount, "Column count does not match dimensions of matrix")
backing = map2(backing, column, { (var row, x) in
row.insert(x, atIndex: index)
return row
})
}
else {
backing = column.map({x in [x]})
}
}
mutating func removeRow(atIndex index: Int){
backing.removeAtIndex(index)
}
mutating func removeColumn(atIndex index: Int){
backing = backing.map({ (var row) in
row.removeAtIndex(index)
return row
})
}
mutating func removeAll() {
backing.removeAll()
}
func generate() -> MatrixGenerator<T> {
return MatrixGenerator(matrix: self)
}
func map<U>(transform: T -> U) -> Matrix<U> {
return Matrix<U>(backing: backing.map({ row in row.map({ x in transform(x) }) }))
}
func map<U>(transform: (T, row: Int, column: Int) -> U) -> Matrix<U> {
return Matrix<U>(backing: backing.enumerate().map{ (r: Int, row: [T]) in
return row.enumerate().map{ (c: Int, x: T) in transform(x, row: r, column: c) }
})
}
func zipWith<U, V>(matrix: Matrix<U>, transform: (T, U) -> V) -> Matrix<V> {
return Matrix<V>(backing: Zip2(backing, matrix.backing).map{ (rowA, rowB) in
return map2(rowA, rowB, transform)
})
}
}
protocol NumericArithmeticType: IntegerLiteralConvertible {
func +(lhs: Self, rhs: Self) -> Self
func -(lhs: Self, rhs: Self) -> Self
func *(lhs: Self, rhs: Self) -> Self
func /(lhs: Self, rhs: Self) -> Self
func +=(inout lhs: Self, rhs: Self)
func -=(inout lhs: Self, rhs: Self)
func *=(inout lhs: Self, rhs: Self)
func /=(inout lhs: Self, rhs: Self)
}
extension Int8 : SignedNumericArithmeticType { }
extension Int16 : SignedNumericArithmeticType { }
extension Int32 : SignedNumericArithmeticType { }
extension Int64 : SignedNumericArithmeticType { }
extension Int : SignedNumericArithmeticType { }
extension UInt8 : NumericArithmeticType { }
extension UInt16 : NumericArithmeticType { }
extension UInt32 : NumericArithmeticType { }
extension UInt64 : NumericArithmeticType { }
extension UInt : NumericArithmeticType { }
protocol FloatingPointArithmeticType : SignedNumericArithmeticType, FloatLiteralConvertible { }
protocol SignedNumericArithmeticType: NumericArithmeticType {
prefix func -(value: Self) -> Self
}
extension Float32 : FloatingPointArithmeticType { }
extension Float64 : FloatingPointArithmeticType { }
extension Float80 : FloatingPointArithmeticType { }
func ==(lhs: (rows:Int, columns:Int), rhs: (rows:Int, columns:Int)) -> Bool {
return lhs.rows == rhs.rows && lhs.columns == rhs.columns
}
prefix func -<T : SignedNumericArithmeticType>(value: Matrix<T>) -> Matrix<T> {
return value.map({ x in 0 - x })
}
func +<T : NumericArithmeticType>(lhs: Matrix<T>, rhs: Matrix<T>) -> Matrix<T> {
assert(lhs.dimensions == rhs.dimensions, "Cannot add matrices of different dimensions")
return lhs.zipWith(rhs, transform: { lhs, rhs in lhs + rhs })
}
func -<T : NumericArithmeticType>(lhs: Matrix<T>, rhs: Matrix<T>) -> Matrix<T> {
assert(lhs.dimensions == rhs.dimensions, "Cannot subract matrices of different dimensions")
return lhs.zipWith(rhs, transform: { lhs, rhs in lhs - rhs })
}
// Scalar product
func *<T : NumericArithmeticType>(matrix: Matrix<T>, scalar: T) -> Matrix<T> {
return matrix.map({ x in x * scalar })
}
func *<T : NumericArithmeticType>(scalar: T, matrix: Matrix<T>) -> Matrix<T> {
return matrix * scalar
}
// Dot product
func *<T : NumericArithmeticType>(lhs: Matrix<T>, rhs: Matrix<T>) -> Matrix<T> {
assert(lhs.dimensions == rhs.dimensions, "Cannot add matrices of different dimensions")
return lhs.zipWith(rhs, transform: { lhs, rhs in lhs * rhs })
}
func determinant<T : SignedNumericArithmeticType>(matrix: Matrix<T>) -> T {
assert(matrix.isSquare, "Cannot find the determinant of a non-square matrix")
assert(!matrix.isEmpty, "Cannot find the determinant of an empty matrix")
// Base case
if matrix.count == 1 { return matrix[0,0] }
else {
// Recursive case
var sum: T = 0
var multiplier: T = 1
let topRow = matrix.rows[0]
for (column, num) in topRow.enumerate() {
var subMatrix = matrix
subMatrix.removeRow(atIndex: 0)
subMatrix.removeColumn(atIndex: column)
sum += num * multiplier * determinant(subMatrix)
multiplier *= (0-1) // swift is buggy
}
return sum
}
}
func cofactor<T : SignedNumericArithmeticType>(matrix: Matrix<T>) -> Matrix<T> {
return matrix.map({ x, r, c in
var subMatrix = matrix
subMatrix.removeRow(atIndex: r)
subMatrix.removeColumn(atIndex: c)
return determinant(subMatrix) * ((r + c % 2 == 0) ? 1 : (0-1)) // swift is buggy
})
}
func adjoint<T : SignedNumericArithmeticType>(matrix: Matrix<T>) -> Matrix<T> {
return cofactor(matrix).transpose
}
func inverse<T : SignedNumericArithmeticType>(matrix: Matrix<T>) -> Matrix<T> {
return cofactor(matrix).transpose * (1 / determinant(matrix))
}
// Matrix multiplication
infix operator ** { associativity left precedence 150 }
func **<T : SignedNumericArithmeticType>(lhs: Matrix<T>, rhs: Matrix<T>) -> Matrix<T> {
assert(lhs.columnCount == rhs.rowCount, "Incombatible dimensions for multiplying matrices")
let cols = rhs.transpose
func multiplyVector(matrix: Matrix<T>, vector: [T]) -> [T] {
return matrix.rows.map({ row in map2(row, vector, { $0 * $1 } ).reduce(0, combine: { $0 + $1 }) });
}
return Matrix(backing: lhs.rows.map({ row in multiplyVector(cols, vector: row) }))
}
func diagonalMatrix<T>(size size: Int, diagonalValue: T, defaultValue: T) -> Matrix<T> {
var matrix = Matrix<T>(rows: size, columns: size, repeatedValue: defaultValue)
for i in 0..<size {
matrix[i,i] = diagonalValue
}
return matrix
}
func identityMatrix<T : SignedNumericArithmeticType>(size: Int) -> Matrix<T> {
return diagonalMatrix(size: size, diagonalValue: 1, defaultValue: 0)
}
struct MatrixGenerator<T> : GeneratorType {
var rowGenerator: EnumerateGenerator<AnyGenerator<[T]>>
var indexGenerator = EnumerateGenerator(anyGenerator(EmptyGenerator<T>()))
var r: Int = 0
init(matrix: Matrix<T>) {
rowGenerator = EnumerateGenerator(anyGenerator(matrix.rows.generate()))
}
mutating func next() -> (T, row: Int, column: Int)? {
if let (c, x) = indexGenerator.next() {
return (x, row: r, column: c)
}
else if let (r, nextRow) = rowGenerator.next() {
self.r = r
self.indexGenerator = EnumerateGenerator(anyGenerator(nextRow.generate()))
return next()
}
else {
return nil
}
}
}
func map2<S : SequenceType, T : SequenceType, U>(lhs: S, _ rhs: T, _ transform: (S.Generator.Element, T.Generator.Element) -> U) -> [U] {
return Array(Zip2(lhs, rhs)).map(transform)
}
@durch
Copy link

durch commented May 10, 2016

First of all thanks for this :), could you elaborate what is the purpose of the final multiplication in cofactor it seems to mess up the inverse...

Thnx

@amasson42
Copy link

amasson42 commented Nov 26, 2016

that's some really interesting and well done work ! I just learn some cool things about swift language thanks to you ;)
Here is some little idea you can add to your class :

  • some convenience initialisers to make a Matrix from the class in the mainly used swift lib : the NSPoint, the SCNVector3 and the SCNMatrix4
  • a methode (or multiple) that apply a function send in parameter to every number in the matrix like :
m.apply {
    (d: Double, y: Int, x: Int) -> Double in
    return (d * ((y + x) % 2 == 0 ? 1 : 0))
}
  • some initialisers with angles for rotations matrix
  • methode to get the eigenvalue and eigenvector
  • the pow function for matrix
  • if you're brave enough a methode that diagonalize your matrice and return 3 matrix in a tuple (but I don't know how to do that)

@JadenGeller
Copy link
Author

Apparently I turned this into a package a while back! My apologies for not linking to it here. It isn't updated to Swift 3, but it is definitely cleaner and more up-to-date than what is here. Pull requests are welcome.

https://github.com/JadenGeller/Dimensional

@jamesporter
Copy link

In the cofactor calculation r + c % 2 looks off... in Swift 4.2

1 + 1 % 2 == 2 probably you want (r + c) % 2 == 0?

@damien-nd
Copy link

damien-nd commented May 27, 2021

I can confirm that you need to use (r + c) but you should also fix the unit test if you use the commented print
println(cofactor(e)) // -> [[24, 5, -4], [-12, 3, 2], [-2, -5, 4]]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment