Swift指南:Map Filter Reduce

此篇為譯文,首次翻譯外文會存在紕漏,見諒...

原文:Swift Guide to Map Filter Reduce

我們要漸漸適應使用 mapfilter 或者 reduce 來對Swift中的集合類型例如 ArrayDictionary 進行操作。除非你有過函數式編程經驗,你應該會更熟悉 for-in 循環。鑒于這一點,這是我對使用 mapfilterreduceflatMap 函數的指南。

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。舉個例子,對DictionarySet 類型使用,返回值一般會是 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類型的兩個參數,它們的類型是由組成這個字典的元素中的 keyvalue 的類型決定。

最后一個例子,關于 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 } }

另外一個例子,利用flatMapmap函數求二維數組的每一個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]

最后一個例子,利用flatMapreduce函數求出二維數組中每個子數組的元素之和,返回一個包含結果的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]

快速小結

下一次你需要遍歷一個集合并做一些事情的時候,優先考慮是否能夠使用mapfilterreduce

  • map函數返回一個包含了對原集合中每一個元素經過映射后的Array

  • filter函數返回一個包含原集合中滿足篩選條件的元素的Array

  • reduce函數返回一個初始參數與原集合元素經過組合后的非集合類型的值。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內容