Swift中的map
、filter
、reduce
可以對Array、Dictionary等集合進行操作。如果你沒有函數式編程經驗,你可能更習慣於使用for-in遍歷。這一篇文章將介紹map
、filter
、reduce
、flatMap
和compactMap
的用法。
Swift中的map
、reduce
和filter
函數來自於函數式編程(functional programming),被稱爲高階函數(high-order function)。高階函數是至少滿足下列一個條件的函數:
- 接受一個或多個函數作爲輸入
- 輸出一個函數
在數學中,也叫做算子。微積分中的導數就是常見的例子,它會映射一個函數到另一個函數。
1. Map
func map<T>(_ transform: (Self.Element) throws -> T) rethrows -> [T]
map
用於遍歷序列(sequence),並對每個元素執行相同操作。map
函數對所有元素執行完映射或轉換後,返回一個包含所有元素的數組,元素類型可以是原來的類型,也可以是新類型。
我們可以使用for-in
循環計算數組元素的平方,如下:
let values = [2.0, 4.0, 5.0, 7.0]
var squares: [Double] = []
for value in values {
squares.append(value*value)
}
for-in
可以解決問題,但代碼有些冗餘。需要聲明指定類型的數組,以便在循環時添加元素;數組還需爲變量。使用map
實現如下:
let squares2 = values.map {$0 * $0}
與for-in
相比,map
有了很大的提升。squares2
是一個常量,Swift可以自動推斷其類型。
剛接觸簡寫的閉包語法時,可能不易理解。map
函數只有一個參數,即尾隨閉包(一個函數)。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
返回數組數據類型可以和原數組不一致。下面將數值類型轉換爲字符串類型:
let scores = [0, 28, 648]
let words = scores.map { NumberFormatter.localizedString(from: $0 as NSNumber, number: .spellOut) }
// ["zero", "twenty-eight", "six hundred forty-eight"]
map
函數除了用於數組,還可用於其他集合類型,例如字典、Set,但返回結果永遠是數組。如下所示:
let milesToPoint = ["point1":120.0,"point2":50.0,"point3":70.0]
let kmToPoint = milesToPoint.map { $1 * 1.6093 }
// [193.11599999999999, 80.465, 112.651]
上面遍歷字典時,閉包的兩個參數分別爲key和value。如果無法區分出參數類型,可以查看Xcode自動補全的提示:
2. Filter
func filter(_ isIncluded: (Self.Element) throws -> Bool) rethrows -> [Self.Element]
filter
函數返回符合指定條件的有序數組。isIncluded
是一個閉包,接收序列的元素,返回Bool
類型值,以指示該元素是否應包含在返回的數組中。filter
複雜度爲O(n)
,n爲序列的長度。
filter
函數只包含一個參數,即閉包。在閉包內添加需滿足的條件,返回值爲Bool
類型。true
表示會把元素添加到結果的數組中;false
表示不會添加。
下面使用filter
過濾奇數,只把偶數添加到數組中:
let digits = [1,4,10,15]
let even = digits.filter { $0 % 2 == 0 }
// [4, 10]
3. Reduce
reduce
將集合中的所有元素合併爲一個新的值。
reduce
函數聲明如下:
func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result
-
initialResult
:作爲初始值,首次調用閉包時傳遞給nextPartialResult
。 -
nextPartialResult
:閉包內將已經組合的值與新的元素合併,並在下一次調用nextPartialResult
閉包時使用,最後返回給調用方。如果集合沒有元素,返回結果爲initialResult
。
reduce(_:_:)
複雜度爲O(n)
。
下面將數組元素值與初始值10相加:
let items = [2.0, 4.0, 5.0, 7.0]
let total = items.reduce(10.0) { partialResult, value in
partialResult + value
}
// 28.0
閉包可以進一步簡化:
let total = items.reduce(10.0, +)
reduce
也可用於拼接數組中的字符串:
let codes = ["abc","def","ghi"]
let text = codes.reduce("1", +)
1abcdefghi
4. FlatMap和CompactMap
flatMap
和compactMap
是map
的變體,適用於以下三種場景:
4.1 flatMap
用於處理序列,並返回序列
Sequence.flatMap<S>(_ transform: (Element) -> S)
-> [S.Element] where S : Sequence
序列調用flatMap
後,每個元素都會執行閉包邏輯,並返回 flatten 結果:
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]
4.2 flatMap
處理可選項
閉包接收可選類型中的非nil值,返回可選類型:
Optional.flatMap<U>(_ transform: (Wrapped) -> U?) -> U?
如果可選類型爲nil
,flatMap
也會返回nil
:
let input: Int? = Int("8")
let passMark: Int? = input.flatMap { $0 > 5 ? $0 : nil }
// Optional(8)
4.3 compactMap
處理序列,返回可選類型
Sequence.compactMap<U>(_ transform: (Element) -> U?) -> U?
這種用途的flatmap
在Swift 4.1(Xcode 9.3)被重命名爲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]
5. 鏈式使用
可以將map
、filter
和reduce
組合使用。例如,計算數組中元素值大於等於5之和,可以先filter
,再reduce
。如下所示:
let marks = [6, 4, 8, 2, 9, 7]
let totalPass = marks.filter{$0 >= 5}.reduce(0, +)
// 30
計算數組元素值平方,並返回偶數。因爲奇數的平方仍然是奇數,可以先過濾掉奇數,再做轉換:
let numbers = [648, 17, 35, 4, 12]
let evenSquares = numbers.filter{$0 % 2 == 0}.map{$0 * $0}
// [419904, 16, 144]
總結
以後遇到for-in
遍歷集合時,可以考慮是否可以使用map
、filter
、reduce
替換:
-
map
:將sequence元素轉換後,以數組形式返回。 -
filter
:只有滿足條件的元素會被放到數組返回。 -
reduce
:每個元素都會調用 combine closure,首次調用時與initialResult
組合。
參考資料:
歡迎更多指正:https://github.com/pro648/tips
本文地址:https://github.com/pro648/tips/blob/master/sources/map、filter、reduce的用法.md