Swift 带有自定义 Array2D 枚举的 for-in 循环 class?

Swift for-in loop with enumerate on custom Array2D class?

我将如何实现一个自定义枚举函数来实现这样的功能 (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

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

enumerate() 函数在 Swift returns 整数中从 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), [=11=]) }
  }
}

但是你在那里复制代码,尤其是从你的生成方法。看到你已经在使用坐标 return 每个元素,为什么不让你的 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 {  }
  }
}

如果您想做的有点过火,您可以编写一个枚举函数来枚举其基数的特定 索引。将其命名为 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!, [=13=]) }
  }

  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
}

利用您已有的 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)")
}

希望这对您有所帮助