Last active
August 10, 2023 10:03
-
-
Save JadenGeller/8c758cbb218a9c4615dd to your computer and use it in GitHub Desktop.
Matrices in Swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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) | |
} |
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)
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.
In the cofactor calculation r + c % 2
looks off... in Swift 4.2
1 + 1 % 2 == 2
probably you want (r + c) % 2 == 0
?
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
First of all thanks for this :), could you elaborate what is the purpose of the final multiplication in
cofactor
it seems to mess up theinverse
...Thnx