Swift中Map Filter Reduce的指导

本文翻译自Swift Guide to Map Filter Reduce

#0

使用mapfilterreduce操作诸如ArrayDictionary的Swift集合可能是之前不常用的。如果你有函数式语言编程经验,你可能知道这与for-in循环类似。这里是我的一些使用指导。

环境为Xcode 9.3 Swift 4.1

Map

map表示循环集合,并对集合中每个元素做相同的操作。map方法返回一个包含对每个元素应用mappingtransform方法的数组:

Swift中Map Filter Reduce的指导_第1张图片
Map

我们可以用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对集合循环操作,返回一个数组,数组仅仅包含匹配条件的元素。

Swift中Map Filter Reduce的指导_第2张图片
Filter

filter方法有个单参数表示条件。闭包把条件应用在集合中的元素上,条件参数的返回值必须是Bool类型。

一个应用filter的例子,筛选数组中的偶数:

let digits = [1,4,10,15]
let even = digits.filter { $0 % 2 == 0 }
// [4, 10]

Reduce

reduce把集合中的每个元素结合在一起,返回一个新的值。

Swift中Map Filter Reduce的指导_第3张图片
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

flatMapCompactMapmap的变化,对结果的展开或者紧凑。这里给了三种应用情况:

  1. 在数列上应用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]
  1. optional上使用flatMap

闭包处理非nil值,返回一个optional

Optional.flatMap(_ transform: (Wrapped) -> U?) -> U?

如果原optionalnil,那么flatMap返回nil

let input: Int? = Int("8")
let passMark: Int? = input.flatMap { $0 > 5 ? $0 : nil }
// 8
  1. 在数列上使用compactMap闭包,返回一个optional
Sequence.compactMap(_ transform: (Element) -> U?) -> U?

注意在Swift 4.1flatMap的这种使用被重命名为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]

总结

下次如果你需要对集合循环操作时,尝试是否可以使用mapfilterreduce

  • map返回一个数组,里面每个元素是对原集合每个元素应用统一操作
  • filter返回一个数组,里面的元素是对原集合的元素应用某个条件筛选后的
  • reduce返回一个值,该值是给定一个初识值并对原集合每个元素与初值计算后的结果

你可能感兴趣的:(Swift中Map Filter Reduce的指导)