Swift:Map,FlatMap,Filter,Reduce指南

此文初始發布在我的簡書

Swift是支持一門函數式編程的語言,擁有MapFlatMap,Filter,Reduce針對集合類型的操作。在使用Objective-C開發時,如果你沒接觸過函數式編程,那你可能沒聽說過這些名詞,希望此篇文章可以幫助你了解Swift中的MapFlatMap,Filter,Reduce

Map

首先我們來看一下mapSwift中的的定義,我們看到它可以用在 OptionalsSequenceType 上(如:數組、詞典等)。

public enum Optional<Wrapped> : _Reflectable, NilLiteralConvertible {
    /// If `self == nil`, returns `nil`.  Otherwise, returns `f(self!)`.
    @warn_unused_result
    public func map<U>(@noescape f: (Wrapped) throws -> U) rethrows -> U?
}

extension CollectionType {
    /// Returns an `Array` containing the results of mapping `transform`
    /// over `self`.
    ///
    /// - Complexity: O(N).
    @warn_unused_result
    public func map<T>(@noescape transform: (Self.Generator.Element) throws -> T) rethrows -> [T]
}

@warn_unused_result:表示如果沒有檢查或者使用該方法的返回值,編譯器就會報警告。
@noescape:表示transform這個閉包是非逃逸閉包,它只能在當前函數map中執行,不能脫離當前函數執行。這使得編譯器可以明確的知道運行時的上下文環境(因此,在非逃逸閉包中可以不用寫self),進而進行一些優化。

Optionals進行map操作

簡要的說就是,如果這個可選值有值,那就解包,調用這個函數,之后返回一個可選值,需要注意的是,返回的可選值類型可以與原可選值類型不一致:

///原來類型: Int?,返回值類型:String?
var value:Int? = 1
var result = value.map { String("result = \($0)") }
/// "Optional("result = 1")"
print(result)
var value:Int? = nil
var result = value.map { String("result = \($0)") }
/// "nil"
print(result)
SequenceType進行map操作

我們可以使用map方法遍歷數組中的所有元素,并對這些元素一一進行一樣的操作(函數方法)。map方法返回完成操作后的數組。


我們可以用For-in完成類似的操作:

var values = [1,3,5,7]
var results = [Int]()
for var value in values {
    value *= 2
    results.append(value)
}
//"[2, 6, 10, 14]"
print(results)

這看起來有點麻煩,我們得先定義一個變量var results然后將values里面的元素遍歷,進行我們的操作以后,將其添加進results,我們比較下使用map又會怎么樣:

let results = values.map ({ (element) -> Int in
    return element * 2
})
//"[2, 6, 10, 14]"

我們向map傳入了一個閉包,對數組中的所有元素都 乘以2,將返回的新的數組賦值為results,是不是精簡了許多?還能更精簡!

精簡寫法

let results = values.map { $0 * 2 }
//"[2, 6, 10, 14]"

what the fuck...沉住氣,讓我們一步步來解析怎么就精簡成這樣了,保證讓你神清氣爽。翻開The Swift Programming Language中對于閉包的定義你就能找到線索。

第一步:

由于閉包的函數體很短,所以我們將其改寫成一行:

let results = values.map ({ (element) -> Int in return element * 2 })
//"[2, 6, 10, 14]"
第二步:

由于我們的閉包是作為map的參數傳入的,系統可以推斷出其參數與返回值,因為其參數必須是(Element) -> Int類型的函數。因此,返回值類型,->及圍繞在參數周圍的括號都可以被忽略:

let results = values.map ({ element  in return element * 2 })
//"[2, 6, 10, 14]"
第三步:

單行表達式閉包可以通過省略return來隱式返回閉包的結果:

let results = values.map ({ element  in element * 2 })
//"[2, 6, 10, 14]"

由于閉包函數體只含有element * 2這單一的表達式,該表達式返回Int類型,與我們例子中map所需的閉包的返回值類型一致(其實是泛型),所以,可以省略return

第四步:

參數名稱縮寫(Shorthand Argument Names),由于Swift自動為內聯閉包提供了參數縮寫功能,你可以直接使用$0,$1,$2...依次獲取閉包的第1,2,3...個參數。
如果您在閉包表達式中使用參數名稱縮寫,您可以在閉包參數列表中省略對其的定義,并且對應參數名稱縮寫的類型會通過函數類型進行推斷。in關鍵字也同樣可以被省略:

let results = values.map ({ $0 * 2 })
//"[2, 6, 10, 14]"

例子中的$0即代表閉包中的第一個參數。

最后一步:

尾隨閉包,由于我們的閉包是作為最后一個參數傳遞給map函數的,所以我們可以將閉包表達式尾隨:

let results = values.map (){ $0 * 2 }
//"[2, 6, 10, 14]"

如果函數只需要閉包表達式一個參數,當您使用尾隨閉包時,您甚至可以把()省略掉:

let results = values.map { $0 * 2 }
//"[2, 6, 10, 14]"

如果還有不明白的,可以多翻閱翻閱The Swift Programming Language

FlatMap

與map一樣,它可以用在 OptionalsSequenceType 上(如:數組、詞典等)。我們先來看看針對Optional的定義:

Optionals進行flatMap操作
public enum Optional<Wrapped> : _Reflectable, NilLiteralConvertible {
    /// Returns `nil` if `self` is `nil`, `f(self!)` otherwise.
    @warn_unused_result
    public func flatMap<U>(@noescape f: (Wrapped) throws -> U?) rethrows -> U?
}

就閉包而言,這里有一個明顯的不同,這次flatMap期望一個 (Wrapped) -> U?)閉包。對于可選值, flatMap 對于輸入一個可選值時應用閉包返回一個可選值,之后這個結果會被壓平,也就是返回一個解包后的結果。本質上,相比 map,flatMap也就是在可選值層做了一個解包。

var value:String? = "1"
var result = value.map { Int($0)}
/// "Optional(Optional(1))"
print(result)
var value:String? = "1"
var result = value.flatMap { Int($0)}
/// ""Optional(1)"
print(result)

使用flatMap就可以在鏈式調用時,不用做額外的解包工作:

var value:String? = "1"
var result = value.flatMap { Int($0)}.map { $0 * 2 }
/// ""Optional(2)"
print(result)
SequenceType進行flatMap操作

我們先來看看Swift中的定義

extension SequenceType {
    /// 返回一個將變換結果連接起來的數組
    /// `transform` over `self`.
    ///     s.flatMap(transform)
    /// is equivalent to
    ///     Array(s.map(transform).flatten())
    @warn_unused_result
    public func flatMap<S : SequenceType>(transform: (Self.Generator.Element) throws -> S) rethrows -> [S.Generator.Element]
}

extension SequenceType {
    /// 返回一個包含非空值的映射變換結果
    @warn_unused_result
    public func flatMap<T>(@noescape transform: (Self.Generator.Element) throws -> T?) rethrows -> [T]
}

通過這兩個描述,就提現了flatMapSequenceType的兩個作用:

一:壓平
var values = [[1,3,5,7],[9]]
let flattenResult = values.flatMap{ $0 }
/// [1, 3, 5, 7, 9]
二:空值過濾
var values:[Int?] = [1,3,5,7,9,nil]
let flattenResult = values.flatMap{ $0 }
/// [1, 3, 5, 7, 9]

Filter

同樣,我先來看看Swift中的定義:

extension SequenceType {
    /// 返回包含原數組中符合條件的元素的數組
    /// Returns an `Array` containing the elements of `self`,
    /// in order, that satisfy the predicate `includeElement`.
    @warn_unused_result
    public func filter(@noescape includeElement: (Self.Generator.Element) throws -> Bool) rethrows -> [Self.Generator.Element]
}

filter函數接受一個(Element) -> Bool)的閉包,來判斷原數組中的元素是否符合條件,這個方法用來過濾數組中的一些元素再好不過了:

var values = [1,3,5,7,9]
let flattenResults = values.filter{ $0 % 3 == 0}
//[3, 9]

我們向flatMap傳入了一個閉包,篩選出了能被3整除的數據。

Reduce

我們先來看下Swift中的定義:

extension SequenceType {
    /// Returns the result of repeatedly calling `combine` with an
    /// accumulated value initialized to `initial` and each element of
    /// `self`, in turn, i.e. return
    /// `combine(combine(...combine(combine(initial, self[0]),
    /// self[1]),...self[count-2]), self[count-1])`.
    @warn_unused_result
    public func reduce<T>(initial: T, @noescape combine: (T, Self.Generator.Element) throws -> T) rethrows -> T
}

給定一個初始化的combine結果,假設為result,從數組的第一個元素開始,不斷地調用combine閉包,參數為:(result,數組中的元素),返回的結果值繼續調用combine函數,直至元素最后一個元素,返回最終的result值。來看下面的代碼(為了更方便你理解這個過程,代碼就不簡寫了):

var values = [1,3,5]
let initialResult = 0
var reduceResult = values.reduce(initialResult, combine: { (tempResult, element) -> Int in
    return tempResult + element
})
print(reduceResult)
//9

我們存在一個數組[1,3,5],給定了一個初始化的結果 initialResult = 0,向reduce函數傳了 (tempResult, element) -> Int的閉包,tempResut便是每次閉包返回的結果值,并且其初始值為我們之前設置的initialResult0element即為我們數組中的元素(可能為1,3,5)。reduce會一直調用combine閉包,直至數組最后一個元素。下面的代碼更形象地描述了整個過程,這其實跟reduce所做的操作是等價的:

func combine(tempResult: Int, element: Int) -> Int  {
    return tempResult + element
}
reduceResult = combine(combine(combine(initialResult, element: 1), element: 3), element: 5)
print(reduceResult)
//9

以上所用的一些示例代碼可以在我的Github中找到,如果您有什么建議可以在評論區留言。

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

推薦閱讀更多精彩內容