5

Using this Stack Overflow question I have the following code.

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

Instead of flattened being set to [1, [2, 3]] I want it to be [1, 2, 3].

What is the easiest/cleanest way to achieve this in Swift?

4
  • 1
    flatMap works with an array of arrays, not an array of Any. Commented Nov 29, 2017 at 3:43
  • @rmaddy Yeah I figured that out, that makes sense to me now. Is there anyway to achieve what I want? Is there some alternative to flatMap that will help me achieve what I want? Commented Nov 29, 2017 at 3:45
  • Possibly solution here: stackoverflow.com/questions/42587629/… Commented Nov 29, 2017 at 6:04
  • Is it so unreasonable to think that flatMap should work like this? If you want to flatten a tree you start with an array that has nodes and subarrays for each node. I don't think this should require an extension. In any case, thanks for the solutions, all! Commented May 24, 2021 at 10:07

3 Answers 3

7

There may be a better way to solve this but one solution is to write your own extension to 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())

Output:

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

This solution will handle any nesting of arrays.

Sign up to request clarification or add additional context in comments.

5 Comments

You can implement this with a flatMap expression: self.flatMap{ ($0 as? [Any]).map{ $0.anyFlatten() } ?? [$0] }
@Alexander That does work, thanks. But it is really inefficient it seems compared to my less "slick" implementation.
Where's the inefficiency? Beyond the overhead of closures (which will be completely optimized away in this case)
@Alexander In a playground testing with the numbers array in my answer, the playground shows my return statement being called 3 times. When I use your one-line implementation of your anyFlatten method, the playground shows the return being called 13 times for the same input. Based on that, it seems inefficient. But I haven't done any rigorous tests.
If I understand your testing method correctly, I think it's quite wrong. Your code's return occurs at the end of every recursive call. The number of time your return statement is called is equal to the sum of depths of all subarrays. In my code, return has the effect of the .append(contentsOf:) and append(_:) calls in your code. Flattening Array(0..<10) (10 elements, all depth 1) would call return 10 times for me (10 elements), and once for you (max depth is 1), but that count measures completely different things
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"]

3 Comments

Would I be able to do the same thing for Strings?
In the event that I am mixing types (not happening now, but curious for future reference) how would that work?
Note that lazy is redundant in both cases as you're using the overloads that return arrays (i.e eagerly evaluating). You could rephrase ($0 is T ? [$0 as! T] : []) as ($0 as? T).map { [$0] } ?? [], though personally I would probably use a switch (e.g gist.github.com/hamishknight/eca9b0be62056284ec37c3d49dd7db65). Also, personally I would make flattened a method, as it's not O(1); though I know you like your computed properties :)
5

Here's an alternate implementation of @rmaddy's anyFlatten:

It can be most concisely written like so, but it's quite cryptic:

extension Array {
    func anyFlatten() -> [Any] {
        return self.flatMap{ ($0 as? [Any]).map{ $0.anyFlatten() } ?? [$0] }
    }
}

Here's a more reasonable implementation:

extension Array {
    func anyFlatten() -> [Any] {
        return self.flatMap{ element -> [Any] in
            if let elementAsArray = element as? [Any] { return elementAsArray.anyFlatten() }
            else { return [element] }
        }
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.