自從我詳細瞭解函數和閉包後,我就想去知道它們的優勢和在編程中的使用, 個人理解是高階函數的使用是基於集合類型的。編程
根據個人理解,高階函數就是把另外一個函數或者閉包當作參數而且返回值。swift
首先,我來解釋一下。思考下面的代碼將會使你對高階函數的理解更深:api
// 將函數當作參數傳遞給另外一個函數
func addation(num1: Double, num2: Double) -> Double {
return num1 + num2
}
func multiply(num1: Double, num2: Double) -> Double {
return num1 * num2
}
func doMathOperation(operation: (_ x: Double, _ y: Double) -> Double, num1: Double, num2: Double) -> Double {
return operation(num1, num2)
}
// print 12
doMathOperation(operation: addation(num1:num2:), num1: 10, num2: 2)
//print 20
doMathOperation(operation: multiply(num1:num2:), num1: 10, num2: 2)
複製代碼
addation(num1:num2:)
、multiply(num1:num2:)
都是(Double,Double)->Double
的函數。addation(num1:num2:)
接受兩個 Double 的值,並返回它們的和;multiply(num1:num2:)
接受兩個 Double 的值,並返回它們的乘積。doMathOperation(operation:num1:num2:)
則是一個接受三個參數的高階函數,它接受兩個 Double 的參數和一個(Double,Double)->Double
類型的函數。看一下該函數的調用,你應該就理解了高階函數的工做原理。數組
在Swift中,函數和閉包是一等公民
。它們能夠存儲在變量中被傳遞。bash
// 函數返回值爲另外一個函數
func doArithmeticOperation(isMultiply: Bool) -> (Double, Double) -> Double {
func addation(num1: Double, num2: Double) -> Double {
return num1 + num2
}
func multiply(num1: Double, num2: Double) -> Double {
return num1 * num2
}
return isMultiply ? multiply : addation
}
let operationToPerform1 = doArithmeticOperation(isMultiply: true)
let operationToPerform2 = doArithmeticOperation(isMultiply: false)
operationToPerform1(10, 2) //20
operationToPerform2(10, 2) //12
複製代碼
在上面的代碼中,doArithmeticOperation(isMultiply:)
是一個返回值類型爲(Double,Double)->Double
的高階函數。它基於一個布爾值來判斷返回值。operationToPerform1 執行乘的操做,operationToPerform2 執行加的操做。經過上面的函數定義和調用,你就理解全部的事情了。閉包
固然,你能夠經過多種不一樣的方法來實現同樣的東西。你可使用閉包來代替函數,你可使用枚舉來判斷函數的操做。在這裏,我只是經過上面的代碼來解釋一下什麼是高階函數。app
下面是 Swift 庫中自帶的一些高階函數,若是我理解沒錯的話,下面的函數接受閉包類型的參數。你能夠在集合類型(Array Set Dictionary)中使用它們。若是你對閉包不太瞭解,你能夠經過這篇文章來學習。函數
map 函數的做用就是對集合進行一個循環,循環內部再對每一個元素作同一個操做。它返回一個包含映射後元素的數組。學習
假設咱們有一個整數型數組:ui
let arrayOfInt = [2,3,4,5,4,7,2]
複製代碼
咱們如何讓每一個數都乘10呢?咱們一般使用 for-in
來遍歷每一個元素,而後執行相關操做。
var newArr: [Int] = []
for value in arrayOfInt { newArr.append(value*10) }
print(newArr) // prints [20, 30, 40, 50, 40, 70, 20]
複製代碼
上面的代碼看着有點冗餘。它包含建立一個新數組的樣板代碼,咱們能夠經過使用 map 來避免。咱們經過 Swift 的自動補全功能能夠看到 map 函數接受有一個 Int 類型的參數並返回一個泛型的閉包。
let newArrUsingMap = arrayOfInt.map { $0 * 10 }
複製代碼
對於一個整型數組,這是 map 的極簡版本。咱們能夠在閉包中使用 $
操做符來代指遍歷的每一個元素。
下面的代碼做用都是同樣的,它們表示了一個從繁到簡的過程。經過下面的代碼,你應該對閉包有了一個清晰的認識。
map 的工做原理:map 函數接受一個閉包做爲參數,在迭代集合時調用該閉包。這個閉包映射集合中的元素,並將結果返回。map 函數再將結果放在數組中返回。
假設咱們有一個書名當作 key ,書的價格當作 value 的字典。
let bookAmount = [「harrypotter」:100.0, 「junglebook」:100.0]
複製代碼
若是你試圖 map 這個字典,Swift 的自動補全將是這樣:
let bookAmount = ["harrypotter": 100.0, "junglebook": 100.0]
let returnFormatMap = bookAmount.map { (key, value) in
return key.capitalized
}
print(returnFormatMap)
//["Junglebook", "Harrypotter"]
複製代碼
咱們經過上面的代碼,對一個字典進行遍歷,每次遍歷在閉包中都有一個 String 類型的 key ,和一個 Double 類型的 value 。返回值爲一個大寫首字母的字符串數組,數組的值還能夠是價格或者元組,這取決於你的需求。
注意:map 函數的返回值類型老是一個泛型數組。你能夠返回包含任意類型的數組。
let lengthInMeters: Set = [4.0, 6.2, 8.9]
let lengthInFeet = lengthInMeters.map { $0 * 3.2808 }
print(lengthInFeet)
//[20.340960000000003, 13.1232, 29.199120000000004]
複製代碼
在上面的代碼中,咱們有一個值類型爲 Double 的 set,咱們的閉包返回值也是 Double 類型。lengthInMeters
是一個 set,而 lengthInFeet
是一個數組。
答案很簡單,你必須在 map 以前調用 enumerate
。
下面是示例代碼:
let numbers = [1, 2, 4, 5]
let indexAndNum = numbers.enumerated().map { (index,element) in
return "\(index):\(element)"
}
print(indexAndNum) // [「0:1」, 「1:2」, 「2:4」, 「3:5」]
複製代碼
filter
會遍歷集合,返回一個包含符合條件元素的數組。
假設咱們要篩選一個整型數組中包含的偶數,你可能會寫下面的代碼:
let arrayOfIntegers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
var newArray = [Int]()
for integer in arrayOfIntegers {
if integer % 2 == 0 {
newArray.append(integer)
}
}
print(newArray) //[2, 4, 6, 8]
複製代碼
就像 map ,這是一個簡單的函數去篩選集合中的元素。
若是咱們對整形數組使用 filter ,Swift 的自動補全展現以下:
如你所見, filter 函數調用了一個接受一個 Int 類型參數、返回值爲 Bool 類型、名字爲 isIncluded
的閉包。isIncluded
在每次遍歷都會返回一個布爾值,而後基於布爾值將建立一個新的包含篩選結果的數組。
咱們能夠經過 filter 將上面的代碼修改成:
var newArray = arrayOfIntegers.filter { (value) -> Bool in
return value % 2 == 0
}
複製代碼
filter 閉包也能夠被簡化,就像 map:
假設有一個書名當作 key ,書的價格當作 value 的字典。
let bookAmount = ["harrypotter":100.0, "junglebook":1000.0]
複製代碼
若是你想對這個字典調用 filter 函數,Swift 的自動補全將是這樣:
filter 函數會調用一個名字爲 isIncluded 的閉包,該閉包接受一個鍵值對做爲參數,並返回一個布爾值。最終,基於返回的布爾值,filter 函數將決定是否將鍵值對添加到數組中。
<重要> 原文中做者寫道:對字典調用 Filter 函數,將返回一個包含元組類型的數組。但譯者在 playground 中發現 返回值實際爲字典類型的數組。
let bookAmount = ["harrypotter": 100.0, "junglebook": 1000.0]
let results = bookAmount.filter { (key, value) -> Bool in
return value > 100
}
print(results) //["junglebook": 1000.0]
複製代碼
還能夠將上述代碼簡化:
//$0 爲 key $1 爲 value
let results = bookAmount.filter { $1 > 100 }
複製代碼
let lengthInMeters: Set = [1, 2, 3, 4, 5, 6, 7, 8, 9]
let lengthInFeet = lengthInMeters.filter { $0 > 5 }
print(lengthInFeet) //[9, 8, 7, 6]
複製代碼
在每次遍歷時, filter 閉包接受一個 Double 的參數,返回一個布爾值。篩選數組中包含的元素基於返回的布爾值。
reduce :聯合集合中全部的值,並返回一個新值。
Apple 的官方文檔以下:
func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result
複製代碼
reduce 函數接受兩個參數:
讓咱們經過一個例子來理解 reduce 的具體做用:
let numbers = [1, 2, 3, 4]
let numberSum = numbers.reduce(0) { (x, y) in
return x + y
}
print(numberSum) // 10
複製代碼
reduce 函數會迭代4次。
1.初始值爲0,x爲0,y爲1 -> 返回 x + y 。因此初始值或者結果變爲 1。
2.初始值或者結果變爲 1,x爲1,y爲2 -> 返回 x + y 。因此初始值或者結果變爲 3。
3.初始值或者結果變爲 3,x爲3,y爲3 -> 返回 x + y 。因此初始值或者結果變爲 6。
4.初始值或者結果變爲 6,x爲6,y爲4 -> 返回 x + y 。因此初始值或者結果變爲 10。
複製代碼
reduce 函數能夠簡化爲:
let reducedNumberSum = numbers.reduce(0) { $0 + $1 }
print(reducedNumberSum) // prints 10
複製代碼
在本例中,閉包的類型爲 (Int,Int)->Int
。因此,咱們能夠傳遞類型爲 (Int,Int)->Int
的任意函數或者閉包。好比咱們能夠把操做符替換爲 -, *, / 等。
let reducedNumberSum = numbers.reduce(0,+) // returns 10
複製代碼
咱們能夠在閉包裏添加 *
或者其餘的操做符。
let reducedNumberSum = numbers.reduce(0) { $0 * $1 }
// reducedNumberSum is 0...
複製代碼
上面的代碼也能夠寫成這樣:
let reducedNumberSum = numbers.reduce(0,*)
複製代碼
reduce 也能夠經過 + 操做符來合併字符串。
let codes = ["abc","def","ghi"]
let text = codes.reduce("") { $0 + $1} //the result is "abcdefghi"
or
let text = codes.reduce("",+) //the result is "abcdefghi"
複製代碼
讓咱們來 reduce bookAmount。
let bookAmount = ["harrypotter":100.0, "junglebook":1000.0]
let reduce1 = bookAmount.reduce(10) { (result, tuple) in
return result + tuple.value
}
print(reduce1) //1110.0
let reduce2 = bookAmount.reduce("book are ") { (result, tuple) in
return result + tuple.key + " "
}
print(reduce2) //book are junglebook harrypotter
複製代碼
對於字典,reduce 的閉包接受兩個參數。
1.一個應該被 reduce 的初始值或結果
2.一個當前鍵值對的元組
複製代碼
reduce2 能夠被簡化爲:
let reducedBookNamesOnDict = bookAmount.reduce("Books are ") { $0 + $1.key + " " } //or $0 + $1.0 + " "
複製代碼
Set 中的 reduce 使用和數組中的一致。
let lengthInMeters: Set = [4.0, 6.2, 8.9]
let reducedSet = lengthInMeters.reduce(0) { $0 + $1 }
print(reducedSet) //19.1
複製代碼
閉包中的返回值類型爲 Double。
Flatmap 用來鋪平 collections 中的 collection 。在鋪平 collection 以前,咱們對每個元素進行 map 操做。 Apple docs 解釋: 返回一個對序列的每一個元素進行形變的串級結果( Returns an array containing the concatenated results of calling the given transformation with each element of this sequence.)
解讀 : map + (Flat the collection)
複製代碼
圖1 對 flatmap 進行代碼說明
圖2
在圖2 中,flatMap 迭代 collections 中的全部 collection 進行大寫操做。在這個例子中,每一個 collection 是字符串。下面是執行步驟:
upperCased()
函數,這相似於:[「abc」,」def」,」ghi」].map { $0.uppercased() }
複製代碼
輸出:
output: [「ABC」, 「DEF」, 「GHI」]
複製代碼
output: ["A", "B", "C", "D", "E", "F", "G", "H", "I"]
複製代碼
注意:在 Swift3 中,flatMap 還能夠自動過濾 nil 值。可是如今已經廢棄該功能。如今用 compactMap 來實現這一功能,稍後在文章中咱們會講到。
如今,你應該明白了 flatMap 是作什麼得了。
let arrs = [[1,2,3], [4, 5, 6]]
let flat1 = arrs.flatMap { return $0 }
print(flat1) //[1, 2, 3, 4, 5, 6]
複製代碼
由於在鋪平以後的返回數組包含元素的類型爲元組。因此咱們不得不轉換爲字典。
let arrs = [["key1": 0, "key2": 1], ["key3": 3, "key4": 4]]
let flat1 = arrs.flatMap { return $0 }
print(flat1)
//[(key: "key2", value: 1), (key: "key1", value: 0), (key: "key3", value: 3), (key: "key4", value: 4)]
var dict = [String: Int]()
flat1.forEach { (key, value) in
dict[key] = value
}
print(dict)
//["key4": 4, "key2": 1, "key1": 0, "key3": 3]
複製代碼
咱們能夠用 flatMap 來實現將一個二維數組鋪平爲一維數組。 flatMap 的閉包接受一個集合類型的參數,在閉包中咱們還能夠進行 filter map reduce 等操做。
let collections = [[5, 2, 7], [4, 8], [9, 1, 3]]
let onlyEven = collections.flatMap { (intArray) in
intArray.filter({ $0 % 2 == 0})
}
print(onlyEven) //[2, 4, 8]
複製代碼
上述代碼的簡化版:
let onlyEven = collections.flatMap { $0.filter { $0 % 2 == 0 } }
複製代碼
咱們能夠鏈式調用高階函數。 不要連接太多,否則執行效率會慢。下面的代碼我在playground中就執行不了。
let arrayOfArrays = [[1, 2, 3, 4], [5, 6, 7, 8, 4]]
let sumOfSquareOfEvenNums = arrayOfArrays.flatMap{$0}.filter{$0 % 2 == 0}.map{$0 * $0}.reduce {0, +}
print(sumOfSquareOfEvenNums) // 136
//這樣能夠運行
let SquareOfEvenNums = arrayOfArrays.flatMap{$0}.filter{$0 % 2 == 0}.map{$0 * $0}
let sum = SquareOfEvenNums.reduce(0 , +) // 136
複製代碼
在迭代完集合中的每一個元素的映射操做後,返回一個非空的數組。
let arr = [1, nil, 3, 4, nil]
let result = arr.compactMap{ $0 }
print(result) //[1, 3, 4]
複製代碼
它對於 Set ,和數組是同樣的做用。
let nums: Set = [1, 2, nil]
let r1 = nums.compactMap { $0 }
print(r1) //[2, 1]
複製代碼
而對於 Dictionary ,它是沒有任何做用的,它只會返回一個元組類型的數組。因此咱們須要使用 compactMapValues 函數。(該函數在Swift5發佈)
let dict = ["key1": nil, "key2": 20]
let result = dict.compactMap{ $0 }
print(result) //[(key: "key1", value: nil), (key: "key2", value: Optional(20))]
let dict = ["key1": nil, "key2": 20]
let result = dict.compactMapValues{ $0 }
print(result)
複製代碼
let arr = [1, nil, 3, 4, nil]
let result = arr.map { $0 }
print(result) //[Optional(1), nil, Optional(3), Optional(4), nil]
複製代碼
使用 map 是這樣的。
好了,到這裏這篇文章也就接近尾聲了。
咱們要儘量的使用高階函數: