Matrix Operations in Swift

I recently completed 10 Days of Statistics on HackerRank. Yay!

In the process, I needed some matrix operations for a medium-difficulty problem. And here they are, code style be damned :

func transpose(_ matrix: [[Double]]) -> [[Double]] {
    let rowCount = matrix.count
    let colCount = matrix[0].count
    var transposed : [[Double]] = Array(repeating: Array(repeating: 0.0, count: rowCount), count: colCount)
    for rowPos in 0..<matrix.count {
        for colPos in 0..<matrix[0].count {
            transposed[colPos][rowPos] = matrix[rowPos][colPos]
        }
    }
    return transposed
}

func multiply(_ A: [[Double]], _ B: [[Double]]) -> [[Double]] {
    let rowCount = A.count
    let colCount = B[0].count
    var product : [[Double]] = Array(repeating: Array(repeating: 0.0, count: colCount), count: rowCount)
    for rowPos in 0..<rowCount {
        for colPos in 0..<colCount {
            for i in 0..<B.count {
                product[rowPos][colPos] += A[rowPos][i] * B[i][colPos]
            }
        }
    }
    return product
}

// gauss jordan inversion
func inverse(_ matrix: [[Double]]) -> [[Double]] {
    // augment matrix
    var matrix = matrix
    var idrow = Array(repeating: 0.0, count: matrix.count)
    idrow[0] = 1.0
    for row in 0..<matrix.count {
        matrix[row] += idrow
        idrow.insert(0.0, at:0)
        idrow.removeLast()
    }
    
    // partial pivot
    for row1 in 0..<matrix.count {
        for row2 in row1..<matrix.count {
            if abs(matrix[row1][row1]) < abs(matrix[row2][row2]) {
                (matrix[row1],matrix[row2]) = (matrix[row2],matrix[row1])
            }
        }
    }
    
    // forward elimination
    for pivot in 0..<matrix.count {
        // multiply
        let arg = 1.0 / matrix[pivot][pivot]
        for col in pivot..<matrix[pivot].count {
            matrix[pivot][col] *= arg
        }
        
        // multiply-add
        for row in (pivot+1)..<matrix.count {
            let arg = matrix[row][pivot] / matrix[pivot][pivot]
            for col in pivot..<matrix[row].count {
                matrix[row][col] -= arg * matrix[pivot][col]
            }
        }
    }
    
    // backward elimination
    for pivot in (0..<matrix.count).reversed() {
        // multiply-add
        for row in 0..<pivot {
            let arg = matrix[row][pivot] / matrix[pivot][pivot]
            for col in pivot..<matrix[row].count {
                matrix[row][col] -= arg * matrix[pivot][col]
            }
        }
    }
    
    // remove identity
    for row in 0..<matrix.count {
        for _ in 0..<matrix.count {
            matrix[row].remove(at:0)
        }
    }
    
    return matrix
}

let X = [ [1.0, 2.0, 3.0], [4.0, 5.0, 11.0], [7.0, 8.0, 9.0] ]
let XI = inverse(X)
let I = multiply(X,XI)
print(I)

That’s the identity matrix popping out at the end, which validates my implementation.

But what’s this? A 60-line method? Uncle Bob would not be pleased.

Comments should not take the place of good variable/method names. Those section comments give clues as to where my methods should be :

func augment(_ matrix: [[Double]]) -> [[Double]] {
    var augmented = matrix
    var idrow = Array(repeating: 0.0, count: matrix.count)
    idrow[0] = 1.0
    for row in 0..<matrix.count {
        augmented[row] += idrow
        idrow.insert(0.0, at:0)
        idrow.removeLast()
    }
    return augmented
}

func deaugment(_ matrix: [[Double]]) -> [[Double]] {
    var deaugmented = matrix
    
    for row in 0..<matrix.count {
        for _ in 0..<matrix.count {
            deaugmented[row].remove(at:0)
        }
    }
    return deaugmented
}

func partialPivot(_ matrix: inout [[Double]]) {
    for row1 in 0..<matrix.count {
        for row2 in row1..<matrix.count {
            if abs(matrix[row1][row1]) < abs(matrix[row2][row2]) {
                (matrix[row1],matrix[row2]) = (matrix[row2],matrix[row1])
            }
        }
    }
}

func scaleRow(_ matrix: inout [[Double]], row: Int, scale: Double) {
    for col in 0..<matrix[row].count {
        matrix[row][col] *= scale
    }
}

func addRow(_ matrix: inout [[Double]], row: Int, scaledBy: Double, toRow: Int) {
    for col in 0..<matrix[row].count {
        matrix[toRow][col] += scaledBy * matrix[row][col]
    }
}

func pivot(_ matrix: inout [[Double]], row pivotRow: Int, col pivotCol: Int, forward: Bool) {
    let scale = 1.0 / matrix[pivotRow][pivotCol]
    scaleRow(&matrix, row: pivotRow, scale: scale)
    
    if forward {
        for toRow in (pivotRow+1)..<matrix.count {
            let scaleBy = -1.0 * matrix[toRow][pivotCol]
            addRow(&matrix, row: pivotRow, scaledBy: scaleBy, toRow: toRow)
        }
    } else {
        for toRow in (0..<pivotRow).reversed() {
            let scaleBy = -1.0 * matrix[toRow][pivotCol]
            addRow(&matrix, row: pivotRow, scaledBy: scaleBy, toRow: toRow)
        }
    }
}

func gaussJordanInverse(_ matrix: [[Double]]) -> [[Double]] {
    var matrix = augment(matrix)
    partialPivot(&matrix)
    
    for p in 0..<matrix.count {
        pivot(&matrix, row: p, col: p, forward: true)
    }
    
    for p in (0..<matrix.count).reversed() {
        pivot(&matrix, row: p, col: p, forward: false)
    }

    matrix = deaugment(matrix)
    
    return matrix
}

let X = [ [1.0, 2.0, 3.0], [4.0, 5.0, 11.0], [7.0, 8.0, 9.0] ]
let XI = gaussJordanInverse(X)
let I = multiply(X,XI)
print(I)

Better. Uncle Bob would be proud (or give me credit for trying, anyway).

One more thing : Thanks to StackOverflow user Alexander, I have an even better way to express that pivot loop :

func pivot(_ matrix: inout [[Double]], row pivotRow: Int, col pivotCol: Int, forward: Bool) {
    let scale = 1.0 / matrix[pivotRow][pivotCol]
    scaleRow(&matrix, row: pivotRow, scale: scale)

    let range = forward ? AnyCollection((pivotRow+1)..<matrix.count) : AnyCollection((0..<pivotRow).reversed())

    for toRow in range {
        let scaleBy = -1.0 * matrix[toRow][pivotCol]
        addRow(&matrix, row: pivotRow, scaledBy: scaleBy, toRow: toRow)
    }
}

iOS Williamsburg – Swift Basics

Do you want to learn Swift syntax, have fun, and meet an enthusiastic group of developers? Come by the Williamsburg Library at 515 Scotland Street, Williamsburg, VA, on Saturday, August 12, 2017. We will be going over Swift basics. We have many experienced developers who are eager to share tips and help newcomers.

https://www.meetup.com/Williamsburg-ios-Development-Meetup/events/241956731/

I hope to see you there!