本文翻译自Swift Guide to Map Filter Reduce
#0
使用map
、filter
、reduce
操作诸如Array
或Dictionary
的Swift集合可能是之前不常用的。如果你有函数式语言编程经验,你可能知道这与for-in
循环类似。这里是我的一些使用指导。
环境为Xcode 9.3 Swift 4.1
Map
map
表示循环集合,并对集合中每个元素做相同的操作。map
方法返回一个包含对每个元素应用mapping
或transform
方法的数组:
我们可以用for-in
循环来计算数组中的每个元素:
let values = [2.0,4.0,5.0,7.0]
var squares: [Double] = []
for value in values {
squares.append(value*value)
}
例子中声明了一个数组squares
,在循环中是比较啰嗦的。我们需要一个squares
数组在循环中保存结果。如果我们使用map
会是这样:
let squares2 = values.map {$0 * $0}
// [4.0, 16.0, 25.0, 49.0]
这是非常大的改善。在map
中我们不需要关注循环,map
会去做。结果值squares
是一个let
或者不可变值,我们不需要申明类型,Swift会推断出来。
括号中的闭包起初很难追踪值。在闭包中,map
方法提供单参数表示集合中的每个值。闭包处理集合中的每个元素并返回结果,map
方法把这些结果返回为一个数组。把map
方法写的更详细能够更容易理解,如下:
let squares3 = values.map({
(value: Double) -> Double in
return value * value
})
这个闭包有个单参数(value: Double)
并且返回一个Double
,其实Swift
可以推断出来。map
中有单参数时我们可以省略括号,写在一行时也可以省略return
:
let squares4 = values.map {value in value * value}
关键字in
这这里是分隔参数和闭包主体的。如果你想进一步省略,可以用数字参数来缩写:
let squares5 = values.map { $0 * $0 }
返回值的类型不一定与原集合中的元素类型一致。这里给个map
的例子,把integers
转为strings
:
let scores = [0,28,124]
let words = scores.map { NumberFormatter.localizedString(from: $0 as NSNumber, number: .spellOut) }
// ["zero", "twenty-eight", "one hundred twenty-four"]
map
操作不仅可以操作数组,任何集合类型都可以操作。比如,在Dictionary
或者Set
中应用map
,结果依然是返回一个Array
。这里是一个Dictionay
的例子:
let milesToPoint = ["point1":120.0,"point2":50.0,"point3":70.0]
let kmToPoint = milesToPoint.map { name,miles in miles * 1.6093 }
快速提示:如果你对理解参数类型有疑惑,Xcode的代码补全可以帮助你
Filter
filter
对集合循环操作,返回一个数组,数组仅仅包含匹配条件的元素。
filter
方法有个单参数表示条件。闭包把条件应用在集合中的元素上,条件参数的返回值必须是Bool
类型。
一个应用filter
的例子,筛选数组中的偶数:
let digits = [1,4,10,15]
let even = digits.filter { $0 % 2 == 0 }
// [4, 10]
Reduce
reduce
把集合中的每个元素结合在一起,返回一个新的值。
reduce
方法有两个变量,第一个是初值,第二个是闭包。例如,初值为10
,把数组的每个值求和:
let items = [2.0,4.0,5.0,7.0]
let total = items.reduce(10.0, +)
// 28.0
这里也可以用 +
操作来缩写:
let codes = ["abc","def","ghi"]
let text = codes.reduce("", +)
// "abcdefghi"
参数是一个闭包,你可以用尾闭包的形式写reduce
:
let names = ["alan","brian","charlie"]
let csv = names.reduce("===") {text, name in "\(text),\(name)"}
// "===,alan,brian,charlie"
FlatMap and CompactMap
flatMap
和CompactMap
是map
的变化,对结果的展开或者紧凑。这里给了三种应用情况:
- 在数列上应用
flatMap
,闭包中返回一个数列
Sequence.flatMap(_ transform: (Element) -> S)
-> [S.Element] where S : Sequence
这可能是我第一次在Swift
中使用flatMap
的情景。应用闭包把数列中的每个元素展开的结果:
let results = [[5,2,7], [4,8], [9,1,3]]
let allResults = results.flatMap { $0 }
// [5, 2, 7, 4, 8, 9, 1, 3]
let passMarks = results.flatMap { $0.filter { $0 > 5} }
// [7, 8, 9]
- 在
optional
上使用flatMap
闭包处理非nil
值,返回一个optional
Optional.flatMap(_ transform: (Wrapped) -> U?) -> U?
如果原optional
是nil
,那么flatMap
返回nil
:
let input: Int? = Int("8")
let passMark: Int? = input.flatMap { $0 > 5 ? $0 : nil }
// 8
- 在数列上使用
compactMap
闭包,返回一个optional
Sequence.compactMap(_ transform: (Element) -> U?) -> U?
注意在Swift 4.1
中flatMap
的这种使用被重命名为compactMap
。这里提供一个简单的方法从数组是剔除nil
值:
let keys: [String?] = ["Tom", nil, "Peter", nil, "Harry"]
let validNames = keys.compactMap { $0 }
validNames
// ["Tom", "Peter", "Harry"]
let counts = keys.compactMap { $0?.count }
counts
// [3, 5, 5]
详情参考Replacing flatMap with compactMap.
Chaining
这些方法都可以链式使用。比如,对一个数组中所有大于等于7的数字求和:
let marks = [4,5,8,2,9,7]
let totalPass = marks.filter{$0 >= 7}.reduce(0, +)
// 24
另一个例子,从数组中选出偶数值并对选出的值平方
let numbers = [20,17,35,4,12]
let evenSquares = numbers.filter{$0 % 2 == 0}.map{$0 * $0}
// [400, 16, 144]
总结
下次如果你需要对集合循环操作时,尝试是否可以使用map
、filter
、reduce
-
map
返回一个数组,里面每个元素是对原集合每个元素应用统一操作 -
filter
返回一个数组,里面的元素是对原集合的元素应用某个条件筛选后的 -
reduce
返回一个值,该值是给定一个初识值并对原集合每个元素与初值计算后的结果