【问题标题】:How to use swift flatMap to filter out optionals from an array如何使用 swift flatMap 从数组中过滤掉选项
【发布时间】:2015-07-04 09:47:49
【问题描述】:

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

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

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

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

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

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

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

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

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

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

【问题讨论】:

  • 您的意思可能是 Swift 1.2 与 1.1,目前还没有 Swift 1.3(或者我错过了什么?)
  • Opps,是的,我想我有 Xcode 6.3...更新的问题 - 谢谢!
  • 虽然你可以使用flatMap { $0 }来删除nils,但真正的问题是应该你。如果你不小心,flatMap 可能会导致错误,所以我建议改用removeNils

标签: swift functional-programming optional higher-order-functions flatmap


【解决方案1】:

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

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

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

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

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 也是如此)是您可能需要显式键入转换的返回值:

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 但不知道你可以用它来过滤这样的选项。对 Swift 2 的非常好的补充!
【解决方案2】:

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

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

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

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

外层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) { $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

【讨论】:

  • 谢谢!鉴于过滤器/映射选项更快,我可能会坚持使用它,因为(对我而言)它最清楚发生了什么。
  • 在 swift 2 中,您也可以直接转到 possibles.flatmap{ $0 }。请参阅文档:public func flatMap&lt;T&gt;(@noescape transform: (Self.Generator.Element) throws -&gt; T?) rethrows -&gt; [T] Flatmap 返回一个 Array,其中包含将 transform 映射到 self 的非零结果。
  • @DanielGalasko:是的,这就是 Fizker 在(现在)接受的答案中所写的。我认为当我写这个答案时不可用。
【解决方案3】:

与问题有关。如果您将flatMap 应用于可选数组,请不要忘记可选或强制解包您的数组,否则它将在Optional 上调用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

【讨论】:

    【解决方案4】:

    你可以使用reduce:

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

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

    【讨论】:

    • 这是对 reduce 的巧妙使用,但是(对我而言)需要太多的精神解开才能看到发生了什么。感谢您的想法!
    【解决方案5】:

    因为这是我似乎最终会做很多事情的事情,所以我正在探索一个通用函数来做到这一点。

    我尝试向 Array 添加扩展,以便可以执行 possibles.unwraped 之类的操作,但无法弄清楚如何在 Array 上进行扩展。取而代之的是使用自定义运算符——这里最困难的部分是试图找出选择哪个运算符。最后我选择了&gt;! 来表明数组正在被过滤&gt; 然后解包!

    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]
    

    【讨论】:

      猜你喜欢
      • 2013-04-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-13
      • 2019-06-15
      • 2017-08-24
      相关资源
      最近更新 更多