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

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

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

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

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

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

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

我一直在尝试通过几种方式使用 flatMap 来做到这一点:

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

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

我更喜欢最后一种方法(因为我不必强制展开 [=15=]!...我对这些感到害怕并不惜一切代价避免它们)除了我需要指定数组类型。

是否有替代方法可以根据上下文确定类型,但没有强制解包?

您可以使用 reduce:

let flattened = possibles.reduce([Int]()) { 
        if let x =  { return [=10=] + [x] } else { return [=10=] } 
    }

你仍然在声明类型,但它稍微不那么突兀。

我还是喜欢第一个解决方案,它只创建一个中间体 大批。可以稍微紧凑的写成

let filtermap = possibles.filter({ [=10=] != nil }).map({ [=10=]! })

但是flatMap()没有类型注释也没有强制 可以展开:

var flatmap3 = possibles.flatMap {
    flatMap([=11=], { [[=11=]] }) ?? []
}

flatMap是数组方法

func flatMap<U>(transform: @noescape (T) -> [U]) -> [U]

里面的flatMap是函数

func flatMap<T, U>(x: T?, f: @noescape (T) -> U?) -> U?

这里是一个简单的性能对比(在Release模式下编译)。 它表明第一种方法更快,大约快了一个因子 共 10 个:

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

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

let s2 = NSDate()
var result2 = possibles.flatMap {
    flatMap([=14=], { [[=14=]] }) ?? []
}
let e2 = NSDate()
println(e2.timeIntervalSinceDate(s2))
// 0.117663979530334

由于这似乎是我最终要做的很多事情,所以我正在探索一个通用函数来执行此操作。

我试图向 Array 添加一个扩展,这样我就可以做类似 possibles.unwraped 的事情,但不知道如何在 Array 上进行扩展。取而代之的是使用自定义运算符——这里最难的部分是试图找出选择哪个运算符。最后我选择了 >! 来显示数组正在被过滤 > 然后展开 !.

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

postfix operator >! {}

postfix func >! <T>(array: Array<T?>) -> Array<T> {
    return array.filter({ [=10=] != nil }).map({ [=10=]! })
}

possibles>!
// [1, 2, 3, 4, 5]

Swift 4.1开始你可以使用compactMap:

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

(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 { [=11=] }

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

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
    }
}

一个警告(对于 Swift 2 也是如此)是您可能需要明确键入转换的 return 值:

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

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

与问题相关。如果您将 flatMap 应用于可选数组,请不要忘记选择性地或强制展开您的数组,否则它将在 Optional 上调用 flatMap 而不是符合 Sequence 协议的对象.我曾经犯过这个错误,例如当你想删除空字符串时:

var texts: [String]? = ["one", "two", "", "three"] // has unwanted empty string

let notFlatMapped = texts.flatMap({ [=10=].count > 0 ? [=10=] : nil })
// ["one", "two", "", "three"], not what we want - calls flatMap on Optional

let flatMapped = texts?.flatMap({ [=10=].count > 0 ? [=10=] : nil })
// ["one", "two", "three"], that's what we want, calls flatMap on Array