2

I am trying to create a Matrix class in Swift but I am getting an error on the self.data[row * columns..<(row + 1) * columns] = data line in my setRow() function. The error is 'Cannot assign value of type '[Double]' to type 'ArraySlice''

struct Matrix: CustomStringConvertible {
    let rows:Int
    let columns:Int
    var data:[Double]

    // Description
    public var description: String {
        var description = ""
        for r in 0..<rows {
            description += data[r * columns..<(r + 1) * columns].description + "\n"
        }
        return description
    }

    // Initialisation
    init(rows: Int, columns: Int) {
        self.rows = rows
        self.columns = columns
        self.data = Array(repeating: 0.0, count: rows * columns)
    }

    init(rows: Int, columns: Int, data:[Double]) {
        assert(data.count == (rows * columns),"Number of elements must equal rows * columns")
        self.rows = rows
        self.columns = columns
        self.data = data
    }

    // Validity
    func validRow(row: Int) -> Bool {
        return row > 0 && row < rows
    }

    func validColumn(column: Int) -> Bool {
        return column > 0 && column < columns
    }

    func validIndex(row: Int, column: Int) -> Bool {
        return validRow(row: row) && validColumn(column: column)
    }

    // Setters and getters
    func get(row: Int, column: Int) -> Double {
        assert(validIndex(row: row,column: column), "Index out of range")
        return data[(row * columns) + column]
    }

    mutating func set(row: Int, column: Int, value: Double) {
        assert(validIndex(row: row,column: column), "Index out of range")
        data[(row * columns) + column] = value
    }

    func getRow(row: Int) -> [Double] {
        assert(validRow(row: row), "Index out of range")
        return Array(data[row * columns..<(row + 1) * columns])
    }

    mutating func setRow(row: Int, data:[Double]) {
        assert(validRow(row: row), "Index out of range")
        assert(data.count == columns, "Data must be same length ans the number of columns")
        self.data[row * columns..<(row + 1) * columns] = data
    }

    // Swapping
    mutating func swapRow(row1: Int, row2: Int) {
        assert(validRow(row: row1) && validRow(row: row2), "Index out of range")
        let holder = getRow(row: row2)
        setRow(row: row2, data: getRow(row: row1))
        setRow(row: row1, data: holder)
    }
}
BenJacob
  • 957
  • 10
  • 31

2 Answers2

3

As the error says, Array's ranged subscript deals with ArraySlice, not Array.

One solution, as @vacawama says, is to just create a slice of the entire input array. This can be done by subscripting with the array's indices:

mutating func setRow(row: Int, data newData: [Double]) {
    assert(validRow(row: row), "Index out of range")
    assert(data.count == columns, "Data must be same length ans the number of columns")
    data[row * columns ..< (row + 1) * columns] = newData[newData.indices]
}

Or in Swift 4, you can take advantage of the ... 'operator', which does the exact same thing:

data[row * columns ..< (row + 1) * columns] = newData[...]

But IMO, a better tool for the job here would be replaceSubrange(_:with:):

mutating func replaceSubrange<C>(_ subrange: Range<Int>, with newElements: C) 
     where Element == C.Element, C : Collection

as it allows you to deal with an arbitrary Collection of new elements, meaning that you could also make setRow generic:

mutating func setRow<C : Collection>(row: Int, data newData: C)
    where C.Iterator.Element == Double { // <- in Swift 4, remove ".Iterator"

    assert(validRow(row: row), "Index out of range")
    assert(data.count == columns, "Data must be same length ans the number of columns")
    data.replaceSubrange(row * columns ..< (row + 1) * columns, with: newData)
}
Hamish
  • 78,605
  • 19
  • 187
  • 280
  • Why does this work? `var a = [1, 2, 3, 4, 5]; a[1..<3] = [6, 7]`. – vacawama Jul 14 '17 at 12:29
  • 1
    @vacawama Because `ArraySlice` is `ExpressibleByArrayLiteral` :) So the array literal `[6, 7]` is inferred to be an `ArraySlice`. – Hamish Jul 14 '17 at 12:29
  • Very helpful, I was also wondering about why the above comment is valid, thank you. – BenJacob Jul 14 '17 at 12:32
  • In that case, couldn't he just do `self.data[row * columns..<(row + 1) * columns] = data[0.. – vacawama Jul 14 '17 at 12:33
  • @vacawama Yup, he could, or even in Swift 4 `= data[...]` – but IMO `replaceSubrange` is better suited for the task, as it lets you deal with arbitrary collections of new elements. I'll also edit in your suggestion though (unless you want to post it as an answer) – Hamish Jul 14 '17 at 12:36
  • Go ahead and edit it in because I never would have come up with that without this comment chain. – vacawama Jul 14 '17 at 12:37
0

Sounds simple. Didn't you want to set just a Double instead of [Double] in your function?

mutating func setRow(row: Int, data: Double) {
        assert(validRow(row: row), "Index out of range")
        assert(data.count == columns, "Data must be same length ans the number of columns")
        self.data[row * columns..<(row + 1) * columns] = data
    }
Oleg Danu
  • 4,149
  • 4
  • 29
  • 47