2

I'm trying to transform the following function into a generic extension for 2D Arrays.

func rotate(_ input: [[Int]]) -> [[Int]]
{
  let length = input[0].count
  var value = Array(repeating: [Int](), count: length)
  for index in 0 ..< length
  {
    value[index] = input.map { $0[index] }.reversed()
  }
  return value
}

I'm specifically stumped as to how to specify the constraints to allow me to access the second dimension. Here's a failed attempt:

extension Array where Element: Collection, Element.Iterator.Element: Collection
{
  private func rotate()
  {
    let count = self[0].count // Element.IndexDistance instead of Int

    // Expression type 'Array<Element>' is ambiguous without more context
    var returnValue = Array(repeating: Element, count: 11)
    for index in 0 ..< count // Element.IndexDistance instead of Int
    {
      returnValue[index] = self.map { $0[index] }.reversed()
    }
    return returnValue
  }
}

1 Answers1

4

The problem is that the compiler doesn't know that your extension is for 2D arrays – it just knows that it's for arrays of collections. Therefore the associated types IndexDistance and Index aren't necessarily Int.

The solution therefore is to constrain your extension so that the Element's IndexDistance and Index are of type Int. This will let you form the range 0..<count, as count will now be of type Int (IndexDistance) – and you will be able to subscript the elements within the map(_:) with Ints (as the subscript expects an Index).

(This will be trivial to do once concrete same-type requirements are supported, as you could simply constrain the Element to be an Array, however this is not yet possible.)

You should also note that your constraint Element.Iterator.Element: Collection is incorrect, as that'll constrain the extension to 3D arrays of collections (An array where the element is a collection, where the elements of that collection are a collection).

Finally, you may have to define a typealias to represent the 'inner element' type of the 2D array, as Swift currently has some limitations when working with nested types directly, for example when creating an empty 2D array of that type.

Therefore, a working version of your current method would look something like this:

extension Array where Element: Collection, Element.Index == Int, Element.IndexDistance == Int {

    private func rotate() -> [[Element.Iterator.Element]] {

        typealias InnerElement = Element.Iterator.Element

        // in the case of an empty array, simply return an empty array
        if self.isEmpty { return [] } 
        let length = self[0].count

        var returnValue = [[InnerElement]](repeating: [InnerElement](), count: length)
        for index in 0..<length {
            returnValue[index] = self.map{ $0[index] }.reversed()
        }
        return returnValue
    }
}

Which, as @MartinR points out below, could be simplified considerably by using a nested map(_:), eliminating the need of a typealias as we no longer need to create a 'result' array:

private func rotate() -> [[Element.Iterator.Element]] {

    if self.isEmpty { return [] }
    let length = self[0].count

    return (0..<length).map { index in
        self.map { $0[index] }.reversed()
    }
}

Although note that the constraining of your extension to only work with Int indices isn't strictly necessary (however makes no practical difference as you're only intending to use this with 2D arrays). Another alternative is to just iterate directly over the inner collection's indices.

In order to do this, you simply have to constrain your extension so that the inner collection's Indices have Elements of the same type as the Index of the collection (in the case of an Array, Indices is a CountableRange<Int>):

extension Array where Element: Collection, Element.Indices.Iterator.Element == Element.Index {
    private func rotate() -> [[Element.Iterator.Element]] {

        if self.isEmpty { return [] }

        return self[0].indices.map { index in
            self.map { $0[index] }.reversed()
        }
    }
}
Community
  • 1
  • 1
Hamish
  • 78,605
  • 19
  • 187
  • 280