How can I flatten an array swiftily in Swift?

Sweeper picture Sweeper · May 14, 2016 · Viewed 7.5k times · Source

I want to turn this:

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

into this:

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

very gracefully.

The most straightforward way, of course, is

var y = [Int]()
x.forEach { y.appendContentsOf($0) }

But that makes the resulting array mutable, which is unnecessary. I don't like this way.

I tried using reduce:

let y = x.reduce([Int]()) { (array, ints) -> [Int] in
    array.appendContentsOf(ints)
    return array
}

But the compiler complains that array is immutable, so I can't call the mutating method appendContentsOf.

Hence, I added some stuff:

let y = x.reduce([Int]()) { (array, ints) -> [Int] in
    var newArr = array
    newArr.appendContentsOf(ints)
    return newArr
}

This is just plain bad. I have an instinct that this is not swifty.

How can I flatten an array more swiftily than the above methods? A one-liner would be good.

Answer

jtbandes picture jtbandes · May 14, 2016

There's a built-in function for this called joined:

[[1, 2, 3], [4, 5, 6], [7, 8, 9]].joined()

(Note that this doesn't actually return another Array, it returns a FlattenSequence, but that usually doesn't matter because it's still a sequence that you can use with for loops and whatnot. If you really care, you can use Array(arrayOfArrays.joined()).)


The flatMap function can also help you out. Its signature is, roughly,

flatMap<S: SequenceType>(fn: (Generator.Element) -> S) -> [S.Generator.Element]

This means that you can pass a fn which for any element returns a sequence, and it'll combine/concatenate those sequences.

Since your array's elements are themselves sequences, you can use a function which just returns the element itself ({ x in return x } or equivalently just {$0}):

[[1, 2, 3], [4, 5, 6], [7, 8, 9]].flatMap{ $0 }