How to flatten an array *this way* swiftly?

2019-01-27 04:31发布

问题:

I asked a similar question a while ago. It was asking how can I turn an array like this:

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

to this:

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

But now I want to turn that same array to this:

[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:

Original array:

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

Result:

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

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.

回答1:

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

extension Collection where Self.Iterator.Element: RandomAccessCollection { 
    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) }


回答2:

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