压平[任意]数组 Swift

5

我使用这个 Stack Overflow 问题,有以下代码。

let numbers = [1,[2,3]] as [Any]
var flattened = numbers.flatMap { $0 }
print(flattened) // [1, [2, 3]]

我希望将flattened设置为[1, 2, 3],而不是[1, [2, 3]]

在Swift中,最简单/最清晰的方法是什么?


1
flatMap适用于一个数组的数组,而不是Any数组。 - rmaddy
@rmaddy 是的,我明白了,现在对我来说有意义了。有什么方法可以实现我的目标吗?有没有一些替代flatMap的方法可以帮助我达到我想要的效果? - Charlie Fish
可能的解决方案在这里:https://stackoverflow.com/questions/42587629/swift-function-taking-generic-array/42599849#42599849 - Martin R
flatMap应该像这样工作,这么想是否不合理?如果您想要展平一棵树,您可以从一个包含节点和每个节点的子数组的数组开始。我认为这不需要扩展。无论如何,感谢所有的解决方案! - Andy Weinstein
3个回答

7
extension Collection where Element == Any {
    var joined: [Any] { flatMap { ($0 as? [Any])?.joined ?? [$0] } }
    func flatMapped<T>(_ type: T.Type? = nil) -> [T] { joined.compactMap { $0 as? T } }
}

let objects: [Any] = [1,[2,3],"a",["b",["c","d"]]]
let joined = objects.joined()   // [1, 2, 3, "a", "b", "c", "d"]

let integers = objects.flatMapped(Int.self)  // [1, 2, 3]
// setting the type explicitly
let integers2: [Int] = objects.flatMapped()        // [1, 2, 3]
// or casting
let strings = objects.flatMapped() as [String]     // ["a", "b", "c", "d"]

我能对字符串做同样的事情吗? - Charlie Fish
如果我混合使用类型(现在不会发生,但是未来可能会),那么会怎样工作? - Charlie Fish
2
请注意,在两种情况下,lazy 都是多余的,因为您正在使用返回数组的重载(即急切评估)。您可以将 ($0 is T ? [$0 as! T] : []) 改写为 ($0 as? T).map { [$0] } ?? [],不过个人而言,我可能会使用 switch(例如 https://gist.github.com/hamishknight/eca9b0be62056284ec37c3d49dd7db65)。另外,就我个人而言,我会将 flattened 设为方法,因为它不是 O(1);但我知道您喜欢计算属性 :) - Hamish

7

解决这个问题可能有更好的方法,但一个解决方案是编写自己的Array扩展程序:

extension Array {
    func anyFlatten() -> [Any] {
        var res = [Any]()
        for val in self {
            if let arr = val as? [Any] {
                res.append(contentsOf: arr.anyFlatten())
            } else {
                res.append(val)
            }
        }

        return res
    }
}

let numbers = [1,[2, [4, 5] ,3], "Hi"] as [Any]
print(numbers.anyFlatten())

输出:

[1, 2, 4, 5, 3, "Hi"]

该解决方案可处理任何嵌套的数组。


1
你可以使用flatMap表达式来实现这个功能:self.flatMap{ ($0 as? [Any]).map{ $0.anyFlatten() } ?? [$0] } - Alexander
@Alexander 那确实可以,谢谢。但是与我不那么“流畅”的实现相比,它似乎效率很低。 - rmaddy
1
哪里存在低效率?除了闭包的开销(在这种情况下将被完全优化掉),还有什么? - Alexander
@Alexander 在游乐场中测试我的答案中的“numbers”数组时,游乐场显示我的“return”语句被调用了3次。当我使用您的“anyFlatten”方法的一行实现时,游乐场显示相同输入的“return”被调用了13次。基于此,它似乎效率低下。但我还没有进行任何严格的测试。 - rmaddy
3
如果我正确理解你的测试方法,我认为它是错误的。你的代码中的 return 语句出现在每次递归调用的末尾。你的 return 语句被调用的次数等于所有子数组深度的总和。在我的代码中,return 的效果相当于你的代码中的 .append(contentsOf:)append(_:) 调用。对于我来说,将数组 Array(0..<10)(10个元素,深度均为 1)展开将会调用 return 10 次(10个元素),而对于你来说只会调用一次(最大深度为1),但这个计数完全测量了不同的东西。 - Alexander

5
以下是@rmaddy的anyFlatten的另一种实现方式:
可以最简洁地写成下面这样,但它相当晦涩:
extension Array {
    func anyFlatten() -> [Any] {
        return self.flatMap{ ($0 as? [Any]).map{ $0.anyFlatten() } ?? [$0] }
    }
}

这是一个更为合理的实现方式:
extension Array {
    func anyFlatten() -> [Any] {
        return self.flatMap{ element -> [Any] in
            if let elementAsArray = element as? [Any] { return elementAsArray.anyFlatten() }
            else { return [element] }
        }
    }
}

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