map、filter、reduce的用法

Swift中的mapfilterreduce可以對Array、Dictionary等集合進行操作。如果你沒有函數式編程經驗,你可能更習慣於使用for-in遍歷。這一篇文章將介紹mapfilterreduceflatMapcompactMap的用法。

Swift中的mapreducefilter函數來自於函數式編程(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

flatMapcompactMapmap的變體,適用於以下三種場景:

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?

如果可選類型爲nilflatMap也會返回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. 鏈式使用

可以將mapfilterreduce組合使用。例如,計算數組中元素值大於等於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遍歷集合時,可以考慮是否可以使用mapfilterreduce替換:

  • map:將sequence元素轉換後,以數組形式返回。
  • filter:只有滿足條件的元素會被放到數組返回。
  • reduce:每個元素都會調用 combine closure,首次調用時與initialResult組合。

參考資料:

  1. Swift Guide to Map Filter Reduce
  2. Map, Reduce and Filter in Swift

歡迎更多指正:https://github.com/pro648/tips

本文地址:https://github.com/pro648/tips/blob/master/sources/map、filter、reduce的用法.md

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章