Swift: мутирующий массив в структуре

Я пытаюсь создать класс Matrix в Swift, но получаю сообщение об ошибке в строке self.data[row * columns..<(row + 1) * columns] = data в моей функции setRow(). Ошибка: «Невозможно присвоить значение типа «[Double]» для типа «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)
    }
}

person BenJacob    schedule 14.07.2017    source источник


Ответы (2)


Как говорится в ошибке, ранжированный индекс Array имеет дело с ArraySlice, а не с Array.

Одно из решений, как говорит @vacawama, состоит в том, чтобы просто создать фрагмент весь входной массив. Это можно сделать, подписавшись на 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]
}

Или в Swift 4 вы можете воспользоваться оператор ..., который делает то же самое:

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

Но IMO, лучшим инструментом для работы здесь будет replaceSubrange(_:with:):

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

поскольку это позволяет вам иметь дело с произвольным Collection новых элементов, а это означает, что вы также можете сделать setRow общим:

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)
}
person Hamish    schedule 14.07.2017
comment
Почему это работает? var a = [1, 2, 3, 4, 5]; a[1..<3] = [6, 7]. - person vacawama; 14.07.2017
comment
@vacawama Потому что ArraySlice равно ExpressibleByArrayLiteral :) Таким образом, литерал массива [6, 7] подразумевается как ArraySlice<Int>. - person Hamish; 14.07.2017
comment
Очень полезно, мне также было интересно, почему приведенный выше комментарий действителен, спасибо. - person BenJacob; 14.07.2017
comment
В таком случае, разве он не мог просто сделать self.data[row * columns..<(row + 1) * columns] = data[0..<data.count]. - person vacawama; 14.07.2017
comment
@vacawama Да, он мог бы, или даже в Swift 4 = data[...], но IMO replaceSubrange лучше подходит для этой задачи, поскольку позволяет вам иметь дело с произвольными наборами новых элементов. Я также отредактирую ваше предложение (если вы не хотите опубликовать его как ответ) - person Hamish; 14.07.2017
comment
Идите вперед и отредактируйте его, потому что я бы никогда не придумал это без этой цепочки комментариев. - person vacawama; 14.07.2017

Звучит просто. Разве вы не хотели установить в своей функции только Double вместо [Double]?

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
    }
person Oleg Danu    schedule 14.07.2017
comment
Нет, я хочу установить массив значений в моем массиве self.data - person BenJacob; 14.07.2017