2

How would I go about implementing a custom enumerate function that makes something like this work (Swift 2):

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

In my simple Array2D struct:

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
        }
    }
}

I couldn't find any good explanation on implementing an enumerate function in Swift

0

2 Answers 2

2

The enumerate() function in Swift returns integers starting from 0 for the first part of its tuple. Those have nothing to do with the sequence you're enumerating over. So, for instance, this won't work:

let word = "hello".characters

for (index, letter) in word.enumerate() {
  print(word[index])
}

Because the indices of a characterView are String.Indexs.

So there are several ways to get what you're going for. The first is to just overload enumerate() for your struct. Again, there are a few days you could do this. First off, how about a function that uses your own generator, and uses its own logic to figure out the coordinates. This could work:

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) }
  }
}

But you're duplicating code there, especially from your generate method. Seeing you're already using coordinates to return each element, why not just have your enumerate method be the default, and your generate method call on that. Something like this:

// 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 }
  }
}

If you wanted to go a little overboard, you could write an enumerate function that enumerates the specific indices of its base. Call it 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)
  }
}

With this function, this would work:

let word = "hello".characters

for (index, letter) in word.specEnumerate() {
  print(word[index])
}

But your matrix struct is still a SequenceType, with no specific indices. For that, you'll have to implement your own 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
  }
}

Right. Now you'll need another matrix struct:

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)
  }
}

Right. So now, after all of that, this works:

let myMatrix = Matrix2D(contents: [[1, 2], [3, 4]])

for (coordinate, value) in myMatrix.specEnumerate() {
  value == myMatrix[coordinate] // True every time
}
Sign up to request clarification or add additional context in comments.

4 Comments

Thank you very much for your detailed explanation! I don't understand, however, what specEnumerate does?
So when you use normal enumerate(), it returns a sequence of tuples, the first element of which is an integer. [1, 2, 3].enumerate() returns (0, 1), (1, 2), (2, 3). However, some types of sequences in Swift have indices that aren't integers - String.characterView for instance. specEnumerate() does the same thing as normal enumerate(), except the first element in the tuple is the index of the base. So, in the example above, it would return (MatrixIndex(x:0, y:0, columns:2), 1), (MatrixIndex(x:1, y:0, columns:2), 2)...
Okay, so that means that it's a generic solution?
Yup! It would work for matrices that have their own index type or anything else with a non-integer index - dictionaries, sets, whatever
0

It might suffice defining your own enumerate taking advantage of the one you already have:

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
    }
}

Notice in this case you could avoid conforming to SequenceType since I use generate from the private array. Anyway it could be consistent to do so.

Here is how then you could use it:

var a2d = Array2D<Int>(columns: 2, rows: 4)
a2d[0,1] = 4

for ((column, row), item) in a2d.enumerate() {
    print ("[\(column) : \(row)] = \(item)")
}

Hope this helps

6 Comments

By the way, your standard generator (the one that conforms to the protocol) does not need to calculate row and columns. You could simplify it returning what is generated from the private array, since the order is the same.
Thank you! I could just return the result of anyGenerator, couldn't I? Is that what you mean by avoiding conforming to SequenceType?
Ups, I'm not encouraging you not to conform to SequenceType. Just notice that enumerate() -> AnySequence<((Int, Int), T?)> is all you need for your specific enumeration to work. If you remove SequenceType and generate() -> AnyGenerator<T?> everything still works. Anyway, I think it is more consistent to declare and implement conformity to SequenceType, given that the serie of items returned by generateand your specific enumerate stay the same.
You're right, AnyGenerator already conforms to SequenceType so you don't need to use AnySequence. And I think by saying avoiding conforming to SequenceType he means that enumerate() would be enough.
Uoa, now I see ... Yes you could make enumerate return AnyGenerator. Maybe I'm too committed to consistency :-) As you can see from the docs, the enumerate from SequenceType returns an EnumerateSequence thus I went for a sequence.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.