如何使用swift flatMap从数组中过滤掉选项

Mat*_*ewS 22 functional-programming optional higher-order-functions flatmap swift

我对flatMap有点困惑(添加到Swift 1.2)

假设我有一些可选类型的数组,例如

let possibles:[Int?] = [nil, 1, 2, 3, nil, nil, 4, 5]
Run Code Online (Sandbox Code Playgroud)

在Swift 1.1中,我会做一个过滤器,然后是这样的地图:

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

我一直尝试使用flatMap这样做:

var flatmap1 = possibles.flatMap({
    return $0 == nil ? [] : [$0!]
})
Run Code Online (Sandbox Code Playgroud)

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

我更喜欢最后一种方法(因为我不必强制解包$0!...我害怕这些并且不惜一切代价避免它们)除了我需要指定数组类型.

是否有一个替代方法可以根据上下文计算出类型,但是没有强制解包?

小智 37

Swift 4.1开始,您可以使用compactMap:

let possibles:[Int?] = [nil, 1, 2, 3, nil, nil, 4, 5]
let actuals = possibles.compactMap { $0 }
Run Code Online (Sandbox Code Playgroud)

(Swift 4.1用compactmap替换了flatMap的一些重载.如果你对此有更多细节感兴趣,那么请看例如:https://useyourloaf.com/blog/replacing-flatmap-with-compactmap/ )

使用Swift 2 b1,您可以轻松完成

let possibles:[Int?] = [nil, 1, 2, 3, nil, nil, 4, 5]
let actuals = possibles.flatMap { $0 }
Run Code Online (Sandbox Code Playgroud)

对于早期版本,您可以使用以下扩展名对其进行填充:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

一个警告(对于Swift 2也是如此)是您可能需要显式键入转换的返回值:

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

有关详细信息,请参阅http://airspeedvelocity.net/2015/07/23/changes-to-the-swift-standard-library-in-2-0-betas-2-5/


Mar*_*n R 15

我仍然喜欢第一个解决方案,它只创建一个中间数组.它可以写得更紧凑

let filtermap = possibles.filter({ $0 != nil }).map({ $0! })
Run Code Online (Sandbox Code Playgroud)

但是flatMap()没有类型注释并且没有强制解包是可能的:

var flatmap3 = possibles.flatMap {
    flatMap($0, { [$0] }) ?? []
}
Run Code Online (Sandbox Code Playgroud)

外部flatMap是数组方法

func flatMap<U>(transform: @noescape (T) -> [U]) -> [U]
Run Code Online (Sandbox Code Playgroud)

内在flatMap就是功能

func flatMap<T, U>(x: T?, f: @noescape (T) -> U?) -> U?
Run Code Online (Sandbox Code Playgroud)

这是一个简单的性能比较(在发布模式下编译).它表明第一种方法更快,大约为10倍:

let count = 1000000
let possibles : [Int?] = map(0 ..< count) { $0 % 2 == 0 ? $0 : nil }

let s1 = NSDate()
let result1 = possibles.filter({ $0 != nil }).map({ $0! })
let e1 = NSDate()
println(e1.timeIntervalSinceDate(s1))
// 0.0169369578361511

let s2 = NSDate()
var result2 = possibles.flatMap {
    flatMap($0, { [$0] }) ?? []
}
let e2 = NSDate()
println(e2.timeIntervalSinceDate(s2))
// 0.117663979530334
Run Code Online (Sandbox Code Playgroud)