【问题标题】:Swift for-in loop with enumerate on custom Array2D class?在自定义 Array2D 类上枚举的 Swift for-in 循环?
【发布时间】:2015-07-13 14:30:13
【问题描述】:

我将如何实现一个自定义枚举函数,使之类的工作(Swift 2):

for ((column, row), item) in Array2D.enumerate() { ... }

在我的简单 Array2D 结构中:

struct Array2D<T> : SequenceType {
    let columns: Int
    let rows: Int
    private var array: Array<T?>

    init(columns: Int, rows: Int) {
        self.columns = columns
        self.rows = rows
        array = Array(count: rows*columns, repeatedValue: nil)
    }

    subscript(column: Int, row: Int) -> T? {
        get {
            return array[columns*row + column]
        }
        set {
            array[columns*row + column] = newValue
        }
    }

    func generate() -> AnyGenerator<T?> {
        var column = 0
        var row = 0

        return anyGenerator() {
            guard row < self.rows else {
                return nil
            }

            let item = self[column, row]

            if ++column == self.columns {
                column = 0
                ++row
            }

            return item
        }
    }
}

我找不到任何关于在 Swift 中实现枚举函数的好解释

【问题讨论】:

    标签: swift swift2


    【解决方案1】:

    Swift 中的enumerate() 函数为其元组的第一部分返回从 0 开始的整数。这些与您枚举的序列无关。因此,例如,这是行不通的:

    let word = "hello".characters
    
    for (index, letter) in word.enumerate() {
      print(word[index])
    }
    

    因为characterView 的索引是String.Indexs。

    所以有几种方法可以得到你想要的。首先是为您的结构重载enumerate()。同样,有几天你可以做到这一点。首先,使用您自己的生成器并使用自己的逻辑来计算坐标的函数怎么样。这可能有效:

    func enumerate() -> AnyGenerator<((Int, Int), T?)> {
      let g = self.generate()
      var coord = -1
      return anyGenerator {
        g.next().map { ((++coord % self.columns, coord / self.columns), $0) }
      }
    }
    

    但是您正在那里复制代码,尤其是从您的 generate 方法中。看到您已经在使用坐标来返回每个元素,为什么不将您的 enumerate 方法设为默认值,然后调用 generate 方法。像这样的:

    // Original generate method, now returns the coords it used
    func enumerate() -> AnyGenerator<((Int, Int), T?)> {
      var column = 0
      var row = 0
    
      return anyGenerator() {
        guard row < self.rows else {
          return nil
        }
    
        let item = self[column, row]
    
        if ++column == self.columns {
          column = 0
          ++row
        }
    
        return ((column, row), item)
      }
    }
    
    // uses enumerate, ignores coords
    
    func generate() -> AnyGenerator<T?> {
      let g = self.enumerate()
      return anyGenerator {
        g.next().map { $1 }
      }
    }
    

    如果你想有点过火,你可以编写一个枚举函数来枚举其基的特定索引。称之为 specEnumerate:

    public struct SpecEnumerateGen<Base : CollectionType> : GeneratorType {
    
      private var eG: Base.Generator
      private let sI: Base.Index
      private var i : Base.Index?
    
      public mutating func next() -> (Base.Index, Base.Generator.Element)? {
        i?._successorInPlace() ?? {self.i = self.sI}()
        return eG.next().map { (i!, $0) }
      }
    
      private init(g: Base.Generator, i: Base.Index) {
        self.eG = g
        self.sI = i
        self.i = nil
      }
    }
    
    public struct SpecEnumerateSeq<Base : CollectionType> : SequenceType {
    
      private let col: Base
      public func generate() -> SpecEnumerateGen<Base> {
        return SpecEnumerateGen(g: col.generate(), i: col.startIndex)
      }
    }
    
    public extension CollectionType {
      func specEnumerate() -> SpecEnumerateSeq<Self> {
        return SpecEnumerateSeq(col: self)
      }
    }
    

    有了这个功能,这个工作:

    let word = "hello".characters
    
    for (index, letter) in word.specEnumerate() {
      print(word[index])
    }
    

    但是您的矩阵结构仍然是SequenceType,没有特定的索引。为此,您必须实现自己的MatrixIndex

    public struct MatrixIndex: BidirectionalIndexType {
    
      public let x, y : Int
    
      private let columns: Int
    
      public func successor() -> MatrixIndex {
        return (x + 1 == columns) ?
          MatrixIndex(x: 0, y: y + 1, columns: columns) :
          MatrixIndex(x: x + 1, y: y, columns: columns)
      }
    
      public func predecessor() -> MatrixIndex {
        return (x == 0) ?
          MatrixIndex(x: columns - 1, y: y - 1, columns: columns) :
          MatrixIndex(x: x - 1, y: y, columns: columns)
      }
    }
    
    public func == (lhs: MatrixIndex, rhs: MatrixIndex) -> Bool {
      return lhs.x == rhs.x && lhs.y == rhs.y
    }
    
    extension MatrixIndex : CustomDebugStringConvertible {
      public var debugDescription: String {
        return "\(x), \(y)"
      }
    }
    
    extension MatrixIndex: RandomAccessIndexType {
      public func advancedBy(n: Int) -> MatrixIndex {
        let total = (y * columns) + x + n
        return MatrixIndex(x: total % columns, y: total / columns, columns: columns)
      }
      public func distanceTo(other: MatrixIndex) -> Int {
        return (other.x - x) + (other.y - y) * columns
      }
    }
    

    没错。现在你需要另一个矩阵结构:

    public struct Matrix2D<T> : MutableCollectionType {
      public var contents: [[T]]
      public subscript(index: MatrixIndex) -> T {
        get {
          return contents[index.y][index.x]
        } set {
          self.contents[index.y][index.x] = newValue
        }
      }
      public var count: Int { return contents[0].count * contents.count }
      public var startIndex: MatrixIndex {
        return MatrixIndex(x: 0, y: 0, columns: contents[0].count)
      }
      public var endIndex: MatrixIndex {
        return MatrixIndex(x: 0, y: contents.endIndex, columns: contents[0].count)
      }
    }
    

    没错。所以现在,毕竟,这行得通:

    let myMatrix = Matrix2D(contents: [[1, 2], [3, 4]])
    
    for (coordinate, value) in myMatrix.specEnumerate() {
      value == myMatrix[coordinate] // True every time
    }
    

    【讨论】:

    • 非常感谢您的详细解释!但是,我不明白 specEnumerate 是做什么的?
    • 所以当你使用普通的 enumerate() 时,它会返回一个元组序列,其中第一个元素是一个整数。 [1, 2, 3].enumerate() 返回(0, 1), (1, 2), (2, 3)。但是,Swift 中的某些类型的序列具有 不是 整数的索引 - 例如 String.characterViewspecEnumerate() 和普通的 enumerate() 做同样的事情,除了元组中的第一个元素是基数的索引。因此,在上面的示例中,它将返回 (MatrixIndex(x:0, y:0, columns:2), 1), (MatrixIndex(x:1, y:0, columns:2), 2)...
    • 好的,也就是说它是一个通用的解决方案?
    • 是的!它适用于具有自己的索引类型的矩阵或任何其他具有非整数索引的矩阵 - 字典、集合等
    【解决方案2】:

    利用已有的enumerate 定义自己的enumerate 就足够了:

    func enumerate() -> AnyGenerator<((Int, Int), T?)> {
        var index = 0
        var g = array.generate()
        return anyGenerator() {
            if let item = g.next() {
                let column = index % self.columns
                let row = index / self.columns
                ++index
                return ((column, row) , item)
            }
            return nil
        }
    }
    

    请注意,在这种情况下,您可以避免遵循 SequenceType,因为我使用的是私有数组中的 generate。无论如何,这样做可能是一致的。

    你可以这样使用它:

    var a2d = Array2D<Int>(columns: 2, rows: 4)
    a2d[0,1] = 4
    
    for ((column, row), item) in a2d.enumerate() {
        print ("[\(column) : \(row)] = \(item)")
    }
    

    希望对你有帮助

    【讨论】:

    • 顺便说一下,你的标准生成器(符合协议的那个)不需要计算行和列。您可以简化它返回从私有数组生成的内容,因为顺序是相同的。
    • 谢谢!我可以只返回 anyGenerator 的结果,不是吗?这就是你所说的避免符合 SequenceType 的意思吗?
    • Ups,我不鼓励你不遵守SequenceType。请注意,enumerate() -&gt; AnySequence&lt;((Int, Int), T?)&gt; 是您的特定枚举工作所需的全部内容。如果您删除 SequenceTypegenerate() -&gt; AnyGenerator&lt;T?&gt; 一切仍然有效。无论如何,我认为声明和实施符合SequenceType 更加一致,因为generate 和您的具体enumerate 返回的一系列项目保持不变。
    • 你是对的,AnyGenerator 已经符合SequenceType 所以你不需要使用AnySequence。而且我认为避免遵守SequenceType 意味着enumerate() 就足够了。
    • Uoa,现在我明白了……是的,你可以让 enumerate 返回 AnyGenerator。也许我太注重一致性了 :-) 正如您从文档中看到的那样,SequenceType 中的 enumerate 返回一个 EnumerateSequence,因此我选择了一个序列。
    猜你喜欢
    • 1970-01-01
    • 2020-12-30
    • 1970-01-01
    • 2019-06-26
    • 1970-01-01
    • 2015-05-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多