此文初始發布在我的簡書。
Swift是支持一門函數式編程的語言,擁有Map
,FlatMap
,Filter
,Reduce
針對集合類型的操作。在使用Objective-C開發時,如果你沒接觸過函數式編程,那你可能沒聽說過這些名詞,希望此篇文章可以幫助你了解Swift中的Map
,FlatMap
,Filter
,Reduce
。
Map
首先我們來看一下map
在Swift
中的的定義,我們看到它可以用在 Optionals 和 SequenceType 上(如:數組、詞典等)。
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一樣,它可以用在 Optionals和 SequenceType 上(如:數組、詞典等)。我們先來看看針對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]
}
通過這兩個描述,就提現了flatMap
對SequenceType的兩個作用:
一:壓平
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
便是每次閉包返回的結果值,并且其初始值為我們之前設置的initialResult
為0
,element
即為我們數組中的元素(可能為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中找到,如果您有什么建議可以在評論區留言。