2D数组扩展Swift 3.1.1

7

我正在尝试在Swift 3.1.1中制作一个Array扩展,支持将对象添加到2D数组的特定索引(即使尚未填充数组)。该扩展还应提供获取特定indexPath处对象的功能。我有这个Swift 2的代码,但似乎无法迁移到Swift 3。以下是Swift 2代码:

extension Array where Element: _ArrayProtocol, Element.Iterator.Element: Any {

    mutating func addObject(_ anObject : Element.Iterator.Element, toSubarrayAtIndex idx : Int) {
        while self.count <= idx {
            let newSubArray = Element()
            self.append(newSubArray) 
        }

        var subArray = self[idx]
        subArray.append(anObject)
    }

    func objectAtIndexPath(_ indexPath: IndexPath) -> Any {
        let subArray = self[indexPath.section]
        return subArray[indexPath.row] as Element.Iterator.Element
    }
}

代码来自于这个答案

首先,在Swift3中不再有_ArrayProtocol。我猜我不再需要where子句了?不确定... - Stefan Stefanov
相关链接:https://swift.org/migration-guide/(也许你已经知道了,但如果不知道的话,这将会是一个很大的帮助) - Eric Aya
我真的不确定如何将代码迁移到Swift 3。我尝试过不使用where子句,而是直接使用Element代替Element.Iterator.Element,但是当我尝试执行let newSubArray = Element()时,会出现“Element cannot be constructed because it has no accessible initiliaziers”的错误提示。我还尝试了将扩展从Array改为Collection,并使用Iterator.Element代替Element,但仍然没有成功。 - Stefan Stefanov
我总感觉语法和其他方面都不太对劲。 - Stefan Stefanov
你能发布你的最佳Swift 3版本吗(即使它还不能编译)? - Lou Franco
显示剩余4条评论
2个回答

6
作为Martin在这里的回答中所说,_ArrayProtocol在Swift 3.1中不再是public,因此意味着您不能将其用作扩展中的约束条件。
在您的情况下,一个简单的替代方法是将ArrayElement限制为RangeReplaceableCollection - 它定义了一个init()要求,意味着"空集合",以及一个append(_:)方法以添加元素到集合中。
extension Array where Element : RangeReplaceableCollection {

    typealias InnerCollection = Element
    typealias InnerElement = InnerCollection.Iterator.Element

    mutating func fillingAppend(
        _ newElement: InnerElement,
        toSubCollectionAtIndex index: Index) {

        if index >= count {
            append(contentsOf: repeatElement(InnerCollection(), count: index + 1 - count))
        }

        self[index].append(newElement)
    }
}

请注意,我们正在将附加操作作为单个调用执行(使用append(contentsOf:)),确保我们最多只需要调整外部数组的大小一次。
要从给定的IndexPath获取元素的方法,您只需将内部元素类型限制为具有Int IndexCollection即可。
// could also make this an extension on Collection where the outer Index is also an Int.
extension Array where Element : Collection, Element.Index == Int {

    subscript(indexPath indexPath: IndexPath) -> Element.Iterator.Element {
        return self[indexPath.section][indexPath.row]
    }
}

请注意,我将其设置为subscript而不是方法,因为我觉得它更符合Array的API。
现在,您可以像这样简单地使用这些扩展:
var arr = [[Int]]()

arr.fillingAppend(6, toSubCollectionAtIndex: 3)
print(arr) // [[], [], [], [6]]

let indexPath = IndexPath(row: 0, section: 3)
print(arr[indexPath: indexPath]) // 6

当然,如果您事先知道外部数组的大小,则fillingAppend(_:toSubCollectionAtIndex :) 方法是多余的,因为您可以通过以下方式创建嵌套数组:

var arr = [[Int]](repeating: [], count: 5)

这将创建一个包含5个空的[Int]元素的[[Int]]数组。


非常感谢您给出的精彩答案!代码运行得很好,正是我所需要的!回答写得非常好,并且有良好的文档说明,我从中学到了很多东西! - Stefan Stefanov

2

没有必要将所有这些想法局限于具体的Array类型。

这是我的解决方案。这次讨论非常棒,因为我刚学习了RangeReplaceableCollection。将(我认为是)最好的两个世界融合起来,我尽可能地将所有操作向下(或向上?)推进了类型层次结构。

正如@Hamish所说,下标运算符适用于远不止Array。但是,没有必要约束索引类型,因此我们必须摆脱IndexPath。我们总是可以用typealias Index2d = ...来简化此操作。

extension Collection where Self.Element: Collection {
    subscript(_ indexTuple: (row: Self.Index, column: Self.Element.Index)) -> Self.Element.Element {
        get {
            return self[indexTuple.row][indexTuple.column]
        }
    }
}

为什么不在最通用的层级(介于“Collection”和“RangeReplaceableCollection”之间)拥有可变版本呢?(不幸的是,我认为当我们重新定义“subscript”时getter无法继承):
extension MutableCollection where Self.Element: MutableCollection {
    subscript(_ indexTuple: (row: Self.Index, column: Self.Element.Index)) -> Self.Element.Element {
        get {
            return self[indexTuple.row][indexTuple.column]
        }
        set {
            self[indexTuple.row][indexTuple.column] = newValue
        }
    }
}

然后,如果您想要进行延迟初始化,请避免使用init:repeatedValue并修改set以具有自动初始化语义。您可以通过整合已接受答案的fillingAppend思想,在两个维度上陷入边界溢出并添加缺少的空元素。
当创建2D初始化程序时,为什么不以自然方式扩展repeating的思想呢?
extension RangeReplaceableCollection where Element: RangeReplaceableCollection {
    init(repeating repeatedVal: Element.Element, extents: (row: Int, column: Int)) {
        let repeatingColumn = Element(repeating: repeatedVal, count: extents.column)
        self.init(repeating: repeatingColumn, count: extents.row)
    }
}

例子用法:

enum Player {
    case first
    case second
}

class Model {
    let playerGrid: Array<Array<Player>> = {
        var p = [[Player]](repeating: .first, extents: (row: 10, column: 10))
        p[(3, 4)] = .second
        print("Player at 3, 4 is: \(p[(row: 3, column: 4)])")
        return p
    }()
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接