如何更快地转置数组?

6
我可以帮您进行翻译。以下是需要翻译的内容:

我想要转置一个如下所示的二维数组:

原始数组:

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

结果:

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

假设所有的子数组长度相同。
如果你还没有注意到,结果中的前三个项目是三个子数组的第一个项目。结果中的第四、五和六个项目是每个子数组的第二个项目。
如果你仍然不理解,也许这会有所帮助:
此时,我有以下内容:
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
}

我认为这不是非常符合Swifty的风格。有什么方法可以用更符合Swifty的方式实现吗?

为了避免出现XY问题,我来解释一下我为什么要这样做。

我正在开发一个桌游,使用HLSpriteKit中的HLGridNode(基本上是一个网格状布局的方块)作为游戏板。为了编辑网格节点的内容,我需要传入一个1D数组的精灵节点,而不是2D数组。

为了让我的生活更轻松,我将模型对象存储在一个2D数组中。这样,我就可以通过以下方式引用左侧5个方块和顶部2个方块所对应的方块:

modelObjects[5][2]

如果我使用.flatMap { $0 }来展开2D数组,并将结果传递给网格节点,modelObjects[5][2]看起来离左边有2个正方形,离顶部有5个正方形。
这不是这个问题的重复,因为那个问题似乎有确定的数组数量可供使用。虽然我可以将我的2D数组放入循环中,并执行那些enumerate().map {...}的操作,但这似乎是一种非常冗长的方法。我认为必须有更简单的方法来处理2D数组。

5
可能是将多个数组合并为一个,按顺序进行索引的重复问题。 - Hamish
我正在开发一款桌游。那是你要解决的问题。你已经解决了数组问题。再花时间在这上面只会分散你实现真正目标的注意力。这并不是说这个问题不有趣。只是解决它并不重要。 - Vince O'Sullivan
1
根据您的编辑:从快速查看“重复候选项”来看,我认为其中有些答案并不假定要使用一定数量的数组。 - Martin R
1
注意:看起来您想要对给定的二维数组进行转置,然后简单地将转置后的数组展平。请查看 SwiftSequence 库,其中包含了这样的转置函数(尽管是 Swift 2)。 - dfrib
2个回答

16

这里是对Shadow Of答案的改进:

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

4
直到将其放在扩展中,才能算是“Swifty” ;) - Alexander
当我尝试编写扩展时,我感到非常疯狂。尝试使用map导致“分段错误”,没有明显的原因,尝试使用indicies导致令人讨厌的“无法下标”..最终我得到了这个可行的版本。我知道如何在Swift 2中编写它,但在Swift 3中它看起来很丑陋。你能否查看我的更新答案,或者指出一些错误或更简单的方法? - Shadow Of
1
让我试一试,稍后。 - Alexander
鉴于 OP 想要将 2D 数组展平并转置,您可以将第一个 map 更改为 flatMap - 然后返回 [T] ;) - Hamish
@Hamish 是的,这对 OP 的问题更合适,但保持回答的组件通用和可重用是很好的。 - Alexander

3

通过转置你的2D矩阵,你可以获得想要的结果,例如,使用以下函数:

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
}

然后应用flatten(在Swift 3中称为joined).

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

使用方法

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

尽量避免使用 0 ..< array.count。你可以直接使用 array.indices。请查看我对你的回答的改进。 - Alexander
2
如果您为Array创建扩展,只需添加约束Element.Indices.Iterator.Element == Element.Index(请参见此问答),并使用@AlexanderMomchliov的扩展实现(将matrix替换为self)。 - Hamish

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接