此篇為譯文,首次翻譯外文會存在紕漏,見諒...
原文:Swift Guide to Map Filter Reduce
我們要漸漸適應使用 map,filter 或者 reduce 來對Swift中的集合類型例如 Array 及 Dictionary 進行操作。除非你有過函數式編程經驗,你應該會更熟悉 for-in 循環。鑒于這一點,這是我對使用 map,filter,reduce,flatMap 函數的指南。
Map
**使用 map 來遍歷集合并對集合中每一個元素進行同樣的操作。 ** 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 變量,現在來比較下使用 map 函數的做法:
let values = [2.0,4.0,5.0,7.0]
let squares = values.map {$0 * $0}
// [4.0, 16.0, 25.0, 49.0]
這是一個很大的提升。我們再也不需要使用for循環,因為 map 函數已經幫我們做了。同時變量 squares 被賦值給一個不可變的值,我們不需要去定義它的類型,因為Swift會自行推斷。
一開始簡化的閉包語法會讓這段代碼難以理解。 map 函數有一個唯一的閉包類型的參數,它會遍歷整個集合。這個閉包會取得集合中的每一個元素作為參數并返回一個經過處理的值。 map 函數將這些經過處理的值以 Array 的形式返回。
以下面這種方式編寫 map 函數可以讓我們更容易的看出發生了什么:
let values = [2.0,4.0,5.0,7.0]
let squares2 = values.map({
(value: Double) -> Double in
return value * value
})
這個閉包有一個參數:(value: Double),Swift可以推斷出閉包的返回值為 Double類型。因為 map 函數只有一個閉包參數,因此我們可以省略掉 ( ** 和 ** ) ,同時省略掉 return:
let squares2 = values.map {value in value * value}
in 關鍵字的作用是將閉包的參數與函數體分開。你可以使用參數編號將代碼簡化得更徹底:
let squares = values.map { $0 * $0 }
返回值的類型不會受限于原數組中元素的類型。下面是一個從整型數組映射到字符串數組的例子:
let scores = [0,28,124]
let words = scores.map { NSNumberFormatter.localizedStringFromNumber($0,
numberStyle: .SpellOutStyle) }
// ["zero", "twenty-eight", "one hundred twenty-four"]
map操作不局限于數組,只要是集合類型你都可以使用map。舉個例子,對Dictionary 或 Set 類型使用,返回值一般會是 Array 類型。下面是一個應用在 Dictionary 上的例子:
let milesToPoint = ["point1":120.0,"point2":50.0,"point3":70.0]
let kmToPoint = milesToPoint.map { name,miles in miles * 1.6093 }
小技巧:假如你對理解閉包中的參數類型感到困難,Xcode中的代碼自動補全功能會幫助你理解:
以上這個情形,我們在映射一個 Dictionary ,所以當我們在遍歷這個集合的時候,我們的閉包有 String類型 和 Double類型的兩個參數,它們的類型是由組成這個字典的元素中的 key 與 value 的類型決定。
最后一個例子,關于 Set 集合:
let lengthInMeters: Set = [4.0,6.2,8.9]
let lengthInFeet = lengthInMeters.map {meters in meters * 3.2808}
我們需要處理的是一個 元素為 Double類型的 Set集合,所以我們的閉包
的參數類型也為 Double。
Filter
filter函數會遍歷一個集合,并返回一個 Array,其中包含了集合中滿足過濾條件的元素。
filter函數中有一個參數指定了過濾條件,它是一個閉包,它會從集合中取得一個元素作為閉包的參數,經過閉包處理后返回一個 Bool類型的值,這個值指示了這個元素是否滿足過濾條件。
一個只保留整型數組中的偶數的例子:
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,combine: +)
// 28.0
//譯者注:Xcode 8.0中Swift語法更為簡潔
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("", combine: +)
// "abcdefghi"
//譯者注:Xcode 8.0中Swift語法更為簡潔
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
最簡單的用法是如同它的名字所描述的那樣將一個二維數組拆開展平:
let collections = [[5,2,7],[4,8],[9,1,3]]
let flat = collections.flatMap { $0 }
// [5, 2, 7, 4, 8, 9, 1, 3]
它可以判斷集合中的不可選值,并將不可選值移出集合:
let people: [String?] = ["Tom",nil,"Peter",nil,"Harry"]
let valid = people.flatMap {$0}
// ["Tom", "Peter", "Harry"]
flatMap的魔力在于你可以拆開展平一個多維數組,并且這個多維數組的子數組可以經過處理,最終返回一個包含所有結果的一維數組。(此處建議看下原文,譯者注)
舉個例子,遍歷一個二維數組并返回一個元素都為偶數的一維數組:
let collections = [[5,2,7],[4,8],[9,1,3]]
let onlyEven = collections.flatMap {
intArray in intArray.filter { $0 % 2 == 0 }
}
// [2, 4, 8]
因為flatMap遍歷的是子數組為整型數組的二維數組,所以它的閉包參數的參數 intArray 是 [Int]類型。這也是為什么你可以將上面的代碼利用簡潔的閉包語法變成以下更簡潔的方式,但我認為這樣做會降低代碼的可讀性:
let onlyEven = collections.flatMap { $0.filter { $0 % 2 == 0 } }
另外一個例子,利用flatMap與map函數求二維數組的每一個Int元素的平方,返回一個Array:
let allSquared = collections.flatMap { $0.map { $0 * $0 } }
以非簡寫的方式:
let allSquared = collections.flatMap {
intArray in intArray.map { $0 * $0 }
}
// [25, 4, 49, 16, 64, 81, 1, 9]
最后一個例子,利用flatMap與reduce函數求出二維數組中每個子數組的元素之和,返回一個包含結果的Array:
let sums = collections.flatMap { $0.reduce(0, combine: +) }
//譯者注:Xcode 8.0中Swift語法更為簡潔
let sums = collections.flatMap { $0.reduce(0,+) }
盡管有人指出最后一個例子用map函數就可以辦到,因為reduce的返回值是Int而不是Array:
let sums = collections.map { $0.reduce(0, combine: +) }
//譯者注:Xcode 8.0中Swift語法更為簡潔
let sums = collections.flatMap { $0.reduce(0,+) }
Chaining
你可以將這些函數鏈接起來使用。例如求數組中大于等于7的元素之和,我們可以先用filter篩選,其次再用reduce求和:
let marks = [4,5,8,2,9,7]
let totalPass = marks.filter{$0 >= 7}.reduce(0,combine: +)
// 24
//譯者注:Xcode 8.0中Swift語法更為簡潔
let marks = [4,5,8,2,9,7]
let totalPass = marks.filter{$0 >= 7}.reduce(0,combine: +)
// 24
另外一個例子,先求數組中的元素的平方,再篩選出偶數:
let numbers = [20,17,35,4,12]
let evenSquares = numbers.map{$0 * $0}.filter{$0 % 2 == 0}
// [400, 16, 144]
快速小結
下一次你需要遍歷一個集合并做一些事情的時候,優先考慮是否能夠使用map,filter或reduce:
map函數返回一個包含了對原集合中每一個元素經過映射后的Array。
filter函數返回一個包含原集合中滿足篩選條件的元素的Array。
reduce函數返回一個初始參數與原集合元素經過組合后的非集合類型的值。