How to use swift flatMap to filter out optionals from an array

MathewS picture MathewS · Apr 25, 2015 · Viewed 16.7k times · Source

I'm a little confused around flatMap (added to Swift 1.2)

Say I have an array of some optional type e.g.

let possibles:[Int?] = [nil, 1, 2, 3, nil, nil, 4, 5]

In Swift 1.1 I'd do a filter followed by a map like this:

let filtermap = possibles.filter({ return $0 != nil }).map({ return $0! })
// filtermap = [1, 2, 3, 4, 5]

I've been trying to do this using flatMap a couple ways:

var flatmap1 = possibles.flatMap({
    return $0 == nil ? [] : [$0!]
})

and

var flatmap2:[Int] = possibles.flatMap({
    if let exercise = $0 { return [exercise] }
    return []
})

I prefer the last approach (because I don't have to do a forced unwrap $0!... I'm terrified for these and avoid them at all costs) except that I need to specify the Array type.

Is there an alternative away that figures out the type by context, but doesn't have the forced unwrap?

Answer

Fizker picture Fizker · Jul 24, 2015

Since Swift 4.1 you can use compactMap:

let possibles:[Int?] = [nil, 1, 2, 3, nil, nil, 4, 5]
let actuals = possibles.compactMap { $0 }

(Swift 4.1 replaced some overloads of flatMap with compactmap. If you are interested in more detail on this then see for example: https://useyourloaf.com/blog/replacing-flatmap-with-compactmap/ )

With Swift 2 b1, you can simply do

let possibles:[Int?] = [nil, 1, 2, 3, nil, nil, 4, 5]
let actuals = possibles.flatMap { $0 }

For earlier versions, you can shim this with the following extension:

extension Array {
    func flatMap<U>(transform: Element -> U?) -> [U] {
        var result = [U]()
        result.reserveCapacity(self.count)
        for item in map(transform) {
            if let item = item {
                result.append(item)
            }
        }
        return result
    }
}

One caveat (which is also true for Swift 2) is that you might need to explicitly type the return value of the transform:

let actuals = ["a", "1"].flatMap { str -> Int? in
    if let int = str.toInt() {
        return int
    } else {
        return nil
    }
}
assert(actuals == [1])

For more info, see http://airspeedvelocity.net/2015/07/23/changes-to-the-swift-standard-library-in-2-0-betas-2-5/