6

I want to transpose an array of arrays like this:

Original array:

[
    [1,2,3],
    [4,5,6],
    [7,8,9]
]

Result:

[
    1,4,7,
    2,5,8,
    3,6,9
]
[1,4,7,2,5,8,3,6,9]

Assume all the subarrays have the same length.

If you haven't noticed already, the first three items in the result is the first item of the three subarrays. The fourth, fifth and sixth items in the result is the second item of each subarray.

If you still don't understand, maybe this will help:

At the moment, I have this:

func flatten(array: [[Int]]) -> [Int] {
    var flat = [Int]()
    for i in 0..<array[0].count {
        for subarray in array {
            flat.append(subarray[i])
        }
    }
    return flat
}

I don't think that is very swfity. How can I do this in a swifty way?

Just to avoid being an XY problem, here's why I want to do this.

I am developing a board game. I am using HLGridNode (It's basically a bunch of squares in a grid-like layout) from HLSpriteKit as the board game's board. To edit the contents of the grid node, I need to pass in an 1D array of sprite nodes, not a 2D array.

To make my life easier, I stored the model objects in a 2D array. This way, I can refer to the sqaure 5 squares from the left and 2 squares from the top just by doing:

modelObjects[5][2]

If I flatten the 2D array using .flatMap { $0 } and pass the result to the grid node, modelObjects[5][2] would appear to be 2 squares from the left and 5 squares from the top.

This is not a duplicate of this because that question seems to have definite number of arrays to work with. Although I can put my 2D array into a loop, and do those enumerate().map {...} stuff, it seems like a really long-winded approach. I think there must be a simpler to do this with 2D arrays.

Troy
  • 690
  • 1
  • 7
  • 25
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • 5
    Possible duplicate of [Combining multiple arrays into one, indexing sequentially](http://stackoverflow.com/questions/39696381/combining-multiple-arrays-into-one-indexing-sequentially) – Hamish Oct 06 '16 at 07:10
  • "I am developing a board game." That is the problem you are trying to solve. You have already solved the array problem. Any more time spent on it is a distraction from your real goal. That's not to say that the problem isn't interesting. It's just not important to solve. – Vince O'Sullivan Oct 06 '16 at 07:20
  • 1
    Re your edit: From a quick look at the "duplicate candidate" it seems to me that it has answers which do *not* assume a definite number of arrays to work with. – Martin R Oct 06 '16 at 07:24
  • 1
    (Agrees with dupe mark) Note: it seems you want to _transpose_ a given 2D array followed by simply flattening the transposed array. Have a look at the [SwiftSequence](https://github.com/oisdk/SwiftSequence/) library, which contains just such a transpose function (although Swift 2). – dfrib Oct 06 '16 at 07:33

2 Answers2

16

Here's an improvement on Shadow Of's answer:

extension Collection where Self.Iterator.Element: RandomAccessCollection {
    // PRECONDITION: `self` must be rectangular, i.e. every row has equal size.
    func transposed() -> [[Self.Iterator.Element.Iterator.Element]] {
        guard let firstRow = self.first else { return [] }
        return firstRow.indices.map { index in
            self.map{ $0[index] }
        }
    }
}

let matrix = [
    [1, 2, 3, 4],
    [5, 6, 7, 8],
    [9, 10, 11, 12],
]
matrix.transposed().forEach{ print($0) }
Alexander
  • 59,041
  • 12
  • 98
  • 151
  • 4
    It's not Swifty until it's in an Extension ;) – Alexander Oct 06 '16 at 09:24
  • When I tried to write extension I get mad. Tries to use `map` resulted "segmentation fault" with no visible reason, tries to use `indicies` resulted annoying "unable subscripts".. finally I get this working version. I know how to write it in swift 2, but here in swift 3 it looks so ugly. Can you look at my updated answer and maybe point some mistakes or way to do it more simple? – Yury Oct 06 '16 at 11:34
  • 1
    Let me take a stab at it, later – Alexander Oct 06 '16 at 17:37
  • Given that OP wants the the 2D array flattened as well as transposed, you could just change the first `map` to a `flatMap` – and then return `[T]` ;) – Hamish Oct 06 '16 at 21:48
  • @Hamish Yeah, that would he more suited to OP's question, but it's nice to keep the components of the answer to be general and reusable – Alexander Oct 07 '16 at 00:53
3

You can receive result you wanted by transpose your 2d matrix, using, for example, this function:

func matrixTranspose<T>(_ matrix: [[T]]) -> [[T]] {
    if matrix.isEmpty {return matrix}
    var result = [[T]]()
    for index in 0..<matrix.first!.count {
        result.append(matrix.map{$0[index]})
    }
    return result
}

and applying flatten (joined in swift 3) then.

let arr = [[1,2,3],[4,5,6],[7,8,9]]
print(matrixTranspose(arr))
// [[1, 4, 7], [2, 5, 8], [3, 6, 9]]

print(matrixTranspose(arr).flatMap{$0})
// [1, 4, 7, 2, 5, 8, 3, 6, 9]

Extension version:

extension Collection where Self.Iterator.Element: Collection {
    var transpose: Array<Array<Self.Iterator.Element.Iterator.Element>> {
        var result = Array<Array<Self.Iterator.Element.Iterator.Element>>()
        if self.isEmpty {return result}

        var index = self.first!.startIndex
        while index != self.first!.endIndex {
            var subresult = Array<Self.Iterator.Element.Iterator.Element>()
            for subarray in self {
                subresult.append(subarray[index])
            }
            result.append(subresult)
            index = self.first!.index(after: index)
        }
        return result
    }
}

with usage

let arr = [[1,2,3],[4,5,6],[7,8,9]]
print(arr.transpose)
// [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
Yury
  • 6,044
  • 3
  • 19
  • 41
  • Try to avoid `0 ..< array.count`. You can just use `array.indices`. Check this out my improvement on your answer. – Alexander Oct 06 '16 at 09:06
  • 2
    If you make the extension for `Array`, you should just be able to add the constraint `Element.Indices.Iterator.Element == Element.Index` (see [this Q&A](http://stackoverflow.com/questions/39179660/swift-2d-array-generic-extension-issue-accessing-2nd-dimension)) and just use @AlexanderMomchliov's implementation for the extension (replace `matrix` with `self`). – Hamish Oct 06 '16 at 21:47