我对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!
...我为这些而感到害怕并且不惜一切代价避免它们)除了我需要指定数组类型
是否有替代方法可以按上下文计算出类型,但是没有强制解包?
答案 0 :(得分:36)
由于 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/
答案 1 :(得分:15)
我仍然喜欢第一个解决方案,它只创建一个中间体 阵列。它可以稍微更紧凑地写成
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?
这是一个简单的性能比较(在发布模式下编译)。 它表明第一种方法更快,大约是一个因素 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
答案 2 :(得分:0)
您可以使用reduce
:
let flattened = possibles.reduce([Int]()) {
if let x = $1 { return $0 + [x] } else { return $0 }
}
你仍然在宣称这种类型,但它的突然性稍差。
答案 3 :(得分:0)
由于这似乎最终做了很多事情,我正在探索一个通用函数来做到这一点。
我尝试添加一个扩展到Array,所以我可以做类似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]
答案 4 :(得分:0)
与问题有关。如果您将flatMap
应用于可选数组,请不要忘记选择或强行拆开数组,否则它将在flatMap
上调用Optional
而不是符合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