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

时间:2022-03-30 22:01:13

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

我对flatMap有点困惑(添加到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:

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

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:

我一直试图用flatMap做几件事:

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.

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

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

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

5 个解决方案

#1


34  

With Swift 2 b1, you can simply do

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

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:

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

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/

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

#2


15  

I still like the first solution, which creates only one intermediate array. It can slightly more compact be written as

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

let filtermap = possibles.filter({ $0 != nil }).map({ $0! })

But flatMap() without type annotation and without forced unwrapping is possible:

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

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

The outer flatMap is the array method

外部flatMap是数组方法

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

and the inner flatMap is the function

内部flatMap是函数

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

Here is a simple performance comparison (compiled in Release mode). It shows that the first method is faster, approximately by a factor of 10:

这是一个简单的性能比较(在发布模式下编译)。它表明第一种方法更快,大约为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

#3


0  

You could use reduce:

你可以使用reduce:

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

You are still kind of declaring the type, but it's slightly less obtrusive.

你仍然有点声明这种类型,但它稍微不那么突兀了。

#4


0  

Since this is something I seem to end up doing quite a lot I'm exploring a generic function to do this.

由于这是我似乎最终做了很多事情,我正在探索一个通用函数来做到这一点。

I tried to add an extension to Array so I could do something like possibles.unwraped but couldn't figure out how to make an extension on an Array. Instead used a custom operator -- hardest part here was trying to figure out which operator to choose. In the end I chose >! to show that the array is being filtered > and then unwrapped !.

我试图添加一个扩展到数组,所以我可以做像possibles.unwraped这样的事情,但无法弄清楚如何在数组上进行扩展。而是使用自定义操作员 - 这里最难的部分是试图找出选择哪个操作员。最后我选择了>!显示数组正在被过滤>然后解开!

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

postfix operator >! {}

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

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

#5


0  

Related to the question. If you are applying flatMap to an optional array, do not forget to optionally or force unwrap your array otherwise it will call flatMap on Optional and not objects conforming to Sequence protocol. I made that mistake once, E.g. when you want to remove empty strings:

与问题有关。如果要将flatMap应用于可选数组,请不要忘记选择或强制解包数组,否则它将调用可选的flatMap而不是符合Sequence协议的对象。我曾犯过一次错误,例如当你想删除空字符串时:

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

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

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

#1


34  

With Swift 2 b1, you can simply do

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

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:

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

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/

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

#2


15  

I still like the first solution, which creates only one intermediate array. It can slightly more compact be written as

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

let filtermap = possibles.filter({ $0 != nil }).map({ $0! })

But flatMap() without type annotation and without forced unwrapping is possible:

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

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

The outer flatMap is the array method

外部flatMap是数组方法

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

and the inner flatMap is the function

内部flatMap是函数

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

Here is a simple performance comparison (compiled in Release mode). It shows that the first method is faster, approximately by a factor of 10:

这是一个简单的性能比较(在发布模式下编译)。它表明第一种方法更快,大约为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

#3


0  

You could use reduce:

你可以使用reduce:

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

You are still kind of declaring the type, but it's slightly less obtrusive.

你仍然有点声明这种类型,但它稍微不那么突兀了。

#4


0  

Since this is something I seem to end up doing quite a lot I'm exploring a generic function to do this.

由于这是我似乎最终做了很多事情,我正在探索一个通用函数来做到这一点。

I tried to add an extension to Array so I could do something like possibles.unwraped but couldn't figure out how to make an extension on an Array. Instead used a custom operator -- hardest part here was trying to figure out which operator to choose. In the end I chose >! to show that the array is being filtered > and then unwrapped !.

我试图添加一个扩展到数组,所以我可以做像possibles.unwraped这样的事情,但无法弄清楚如何在数组上进行扩展。而是使用自定义操作员 - 这里最难的部分是试图找出选择哪个操作员。最后我选择了>!显示数组正在被过滤>然后解开!

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

postfix operator >! {}

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

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

#5


0  

Related to the question. If you are applying flatMap to an optional array, do not forget to optionally or force unwrap your array otherwise it will call flatMap on Optional and not objects conforming to Sequence protocol. I made that mistake once, E.g. when you want to remove empty strings:

与问题有关。如果要将flatMap应用于可选数组,请不要忘记选择或强制解包数组,否则它将调用可选的flatMap而不是符合Sequence协议的对象。我曾犯过一次错误,例如当你想删除空字符串时:

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

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

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