一. Array常用的內(nèi)置函數(shù)
1. map
遍歷數(shù)組中的元素镀裤,傳入到后面的閉包里面,閉包的返回值組成新的數(shù)組缴饭,最后返回這個(gè)新數(shù)組暑劝。
var arr = [1, 2, 3, 4]
// [2, 4, 6, 8]
var arr2 = arr.map { $0 * 2 } //重新映射
也可以傳入一個(gè)函數(shù),如下:
func double(_ i: Int) -> Int { i * 2 }
var arr = [1, 2, 3, 4]
// [2, 4, 6, 8]
print(arr.map(double))
① map和flatMap(緊貼颗搂、平坦担猛、展開)的區(qū)別
map:無論你給我返回的是什么我都把返回的東西放到新數(shù)組里面。
flatMap:如果你給我返回的是數(shù)組,我會(huì)把數(shù)組攤開傅联,將數(shù)組里面的元素放到新數(shù)組里面智嚷。
var arr = [1, 2, 3]
// [[1], [2, 2], [3, 3, 3]]
var arr2 = arr.map { Array.init(repeating: $0, count: $0) }
// [1, 2, 2, 3, 3, 3]
var arr3 = arr.flatMap { Array.init(repeating: $0, count: $0) }
② compactMap(壓縮、結(jié)實(shí))
compactMap會(huì)將新數(shù)組中的元素解包纺且,如果是nil就清除這個(gè)元素盏道,最后返回新數(shù)組。
var arr = ["123", "test", "jack", "-30"]
// [Optional(123), nil, nil, Optional(-30)]
var arr2 = arr.map { Int($0) }
// [123, -30]
var arr3 = arr.compactMap { Int($0) } //壓縮
③ map的lazy的優(yōu)化
let arr = [1, 2, 3]
let result = arr.lazy.map { //有l(wèi)azy
(i: Int) -> Int in
print("mapping \(i)")
return i * 2
}
print("begin-----")
print("mapped", result[0])
print("mapped", result[1])
print("mapped", result[2])
print("end----")
如果沒有l(wèi)azy载碌,打印如下猜嘱,可以發(fā)現(xiàn),在begin之前嫁艇,arr中的元素已經(jīng)映射完了朗伶,但是有時(shí)候這樣比較浪費(fèi)和耗時(shí)。
mapping 1
mapping 2
mapping 3
begin-----
mapped 2
mapped 4
mapped 6
end----
如果有l(wèi)azy步咪,打印如下论皆,用到哪個(gè)元素才映射哪個(gè)元素。
begin-----
mapping 1
mapped 2
mapping 2
mapped 4
mapping 3
mapped 6
end----
2. filter
遍歷數(shù)組中的元素猾漫,傳入到后面的閉包里面点晴,閉包的返回值如果是true,就把這個(gè)元素放到新數(shù)組里面悯周,閉包的返回值如果是false粒督,繼續(xù)遍歷數(shù)組中元素,最后返回這個(gè)新數(shù)組禽翼。
var arr = [1, 2, 3, 4]
// [2, 4]
var arr2 = arr.filter { $0 % 2 == 0 } //過濾
3. reduce
這個(gè)函數(shù)比較復(fù)雜屠橄,查看reduce函數(shù)定義如下:
@inlinable public func reduce<Result>(_ initialResult: Result,
_ nextPartialResult: (Result, Self.Element) throws -> Result) rethrows -> Result
可以發(fā)現(xiàn),reduce函數(shù)有兩個(gè)參數(shù)闰挡,第一個(gè)參數(shù)是Result锐墙,第二個(gè)參數(shù)是個(gè)閉包,閉包第一個(gè)參數(shù)是Result长酗,第二個(gè)參數(shù)是數(shù)組中的元素溪北。
如下,如果arr.reduce(0)花枫,最后算出的就是整個(gè)數(shù)組元素的和:
var arr = [1, 2, 3, 4]
var arr2 = arr.reduce(0) {
(result, element) -> Int in
return result + element;
} //尾隨閉包
print(arr2) //0 + 1 + 2 + 3 + 4 = 10
上面代碼也可以簡(jiǎn)寫如下:
var arr = [1, 2, 3, 4]
// 10
var arr2 = arr.reduce(0) { $0 + $1 } //尾隨閉包
// 10
var arr2 = arr.reduce(0, +)
擴(kuò)展:使用reduce實(shí)現(xiàn)map刻盐、filter的功能
var arr = [1, 2, 3, 4]
//[2, 4, 6, 8]
print(arr.map { $0 * 2 })
print(arr.reduce([]) { $0 + [$1 * 2] }) //和上面map一樣
//[2, 4]
print(arr.filter { $0 % 2 == 0 })
print(arr.reduce([]) { $1 % 2 == 0 ? $0 + [$1] : $0 }) //和上面filter一樣
二. 可選類型的map和flatMap
可選類型也有map和flatMap,可選類型的map和flatMap就是將.map前?的參數(shù)解包(就是$0)劳翰,解包成功后的值傳給后?的函數(shù)敦锌,解包失敗直接返回nil。
var num1: Int? = 10
// Optional(20)
var num2 = num1.map { $0 * 2 }
var num3: Int? = nil
// nil
var num4 = num3.map { $0 * 2 }
1. 區(qū)別
可選類型的map和flatMap佳簸,如果解包成功乙墙,map會(huì)將解包后的值包裝成可選項(xiàng)颖变,flatMap不會(huì)。
var num1: Int? = 10
// Optional(Optional(20))
var num2 = num1.map { Optional.some($0 * 2) }
// Optional(20)
var num3 = num1.flatMap { Optional.some($0 * 2) }
下面num2听想、num3是等價(jià)的
var num1: Int? = 10
var num2 = (num1 != nil) ? (num1! + 10) : nil
var num3 = num1.map { $0 + 10 }
// num2腥刹、num3是等價(jià)的
2. 實(shí)際使用
可選類型使用flatMap可以減少一些判斷是否為nil再執(zhí)行三目運(yùn)算的操作。
var fmt = DateFormatter()
fmt.dateFormat = "yyyy-MM-dd"
var str: String? = "2011-09-10"
// old
var date1 = str != nil ? fmt.date(from: str!) : nil
// new 用flatMap汉买,如果返回的是可選類型就不會(huì)再包裝一層
var date2 = str.flatMap{fmt.date(from: $0)}
var date3 = str.flatMap{fmt.date} //更簡(jiǎn)潔
var score: Int? = 98
// old
var str1 = score != nil ? "socre is \(score!)" : "No score"
// new 如果不為nil就將$0包裝成可選字符串衔峰,如果為nil就返回"No score"
var str2 = score.map { "score is \($0)" } ?? "No score"
struct Person {
var name: String
var age: Int
}
var items = [
Person(name: "jack", age: 20),
Person(name: "rose", age: 21),
Person(name: "kate", age: 22)
]
// old
func getPerson1(_ name: String) -> Person? {
let index = items.firstIndex { $0.name == name }
return index != nil ? items[index!] : nil
}
// new 索引如果有值就會(huì)調(diào)?.map,索引沒值就直接返回nil
func getPerson2(_ name: String) -> Person? {
//第一個(gè)$0代表items中的元素蛙粘,第二個(gè)$0代表index
return items.firstIndex { $0.name == name}.map { items[$0] }
}
struct Person {
var name: String
var age: Int
init?(_ json: [String : Any]) {
guard let name = json["name"] as? String,let age = json["age"] as? Int
else {
return nil
}
self.name = name
self.age = age
}
}
var json: Dictionary? = ["name" : "Jack", "age" : 10]
// old
var p1 = json != nil ? Person(json!) : nil
// new
var p2 = json.flatMap(Person.init)
三. 函數(shù)式編程(Funtional Programming)
函數(shù)式編程(Funtional Programming垫卤,簡(jiǎn)稱FP)是一種編程范式,也就是如何編寫程序的方法論
主要思想:把計(jì)算過程盡量分解成一系列可復(fù)用函數(shù)的調(diào)用
主要特征:函數(shù)是“第一等公民”出牧,函數(shù)與其他數(shù)據(jù)類型一樣的地位穴肘,可以賦值給其他變量,也可以作為函數(shù)參數(shù)舔痕、函數(shù)返回值
函數(shù)式編程最早出現(xiàn)在LISP語言评抚,絕大部分的現(xiàn)代編程語言也對(duì)函數(shù)式編程做了不同程度的支持,比如:
Haskell伯复、JavaScript慨代、Python、Swift边翼、Kotlin鱼响、Scala等
函數(shù)式編程中幾個(gè)常用的概念:
Higher-Order Function、Function Currying
Functor组底、Applicative Functor、Monad
參考資料:
http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html
http://www.mokacoding.com/blog/functor-applicative-monads-in-pictures
1. FP實(shí)踐 – 傳統(tǒng)寫法
假設(shè)要實(shí)現(xiàn)以下功能:[(num + 3) * 5 - 1] % 10 / 2
var num = 1
func add(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
func sub(_ v1: Int, _ v2: Int) -> Int { v1 - v2 }
func multiple(_ v1: Int, _ v2: Int) -> Int { v1 * v2 }
func divide(_ v1: Int, _ v2: Int) -> Int { v1 / v2 }
func mod(_ v1: Int, _ v2: Int) -> Int { v1 % v2 }
divide(mod(sub(multiple(add(num, 3), 5), 1), 10), 2) //4
2. FP實(shí)踐 – 函數(shù)式寫法
傳統(tǒng)寫法嵌套比較麻煩筐骇,下面將上面的函數(shù)柯里化(將一個(gè)接受多參數(shù)的函數(shù)變換為一系列只接受單個(gè)參數(shù)的函數(shù))
下面是柯里化后的函數(shù)债鸡,只接收一個(gè)參數(shù),返回一個(gè)函數(shù)
func add(_ v: Int) -> (Int) -> Int { { $0 + v } } //加
func sub(_ v: Int) -> (Int) -> Int { { $0 - v } } //減
func multiple(_ v: Int) -> (Int) -> Int { { $0 * v } } //乘
func divide(_ v: Int) -> (Int) -> Int { { $0 / v } } //除
func mod(_ v: Int) -> (Int) -> Int { { $0 % v } } //取余
let fn1 = add(3) //加3
let fn2 = multiple(6) //乘6
let fn3 = sub(1) //減1
let fn4 = mod(10) //對(duì)10取余
let fn5 = divide(2) //除以2
//num -> fn1 -> fn2 -> fn3 -> fn4 -> fn5
print(fn5(fn4(fn3(fn2(fn1(num)))))) //4
這樣寫也是麻煩铛纬,所以?定義運(yùn)算符厌均,把A->B和B->C的函數(shù)合成?個(gè)函數(shù),??只有?個(gè)A->C
//函數(shù)合成
infix operator >>> : AdditionPrecedence //優(yōu)先級(jí)和加法一樣
func >>><A, B, C>(_ f1: @escaping (A) -> B,
_ f2: @escaping (B) -> C) -> (A) -> C {
{ f2(f1($0)) } //$0就是num
}
var fn = add(3) >>> multiple(5) >>> sub(1) >>> mod(10) >>> divide(2)
fn(num)
這樣就通過函數(shù)式思想實(shí)現(xiàn)了:[(num + 3) * 5 - 1] % 10 / 2告唆,可以發(fā)現(xiàn)對(duì)比傳統(tǒng)寫法簡(jiǎn)潔了很多棺弊。
3. 高階函數(shù)(Higher-Order Function)
高階函數(shù)是至少滿足下列一個(gè)條件的函數(shù):
- 接受一個(gè)或多個(gè)函數(shù)作為輸入(map、filter擒悬、reduce等)
- 返回一個(gè)函數(shù)
FP中到處都是高階函數(shù)模她,如下:
func add(_ v: Int) -> (Int) -> Int { { $0 + v } }
4. 柯里化(Currying)
什么是柯里化?
將一個(gè)接受多參數(shù)的函數(shù)變換為一系列只接受單個(gè)參數(shù)的函數(shù)
兩個(gè)數(shù)相加函數(shù):
func add1(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
add(20, 10)
柯里化之后懂牧,如下:
func add1(_ v: Int) -> (Int) -> Int { { $0 + v } }
add(20)(10) //10 + 20 = 30
Tip:Array侈净、Optional的map方法接收的參數(shù)就是一個(gè)柯里化函數(shù)(只接收?個(gè)參數(shù),返回?個(gè)東?)
三個(gè)數(shù)相加函數(shù)的柯?化如下:
//傳統(tǒng)的
func add2(_ v1: Int, _ v2: Int, _ v3: Int) -> Int { v1 + v2 + v3 }
//柯?化
//v3 == 30
func add2(_ v3: Int) -> (Int) -> (Int) -> (Int) {
//v2 == 20
return { v2 in
//v1 == 10
return { v1 in
return v3 + v2 + v1
}
}
} //最?層在做事情,外層在不斷減少參數(shù)
add2(30)(20)(10) //10 + 20 + 30 = 60
- ?動(dòng)將函數(shù)柯?化
上面的add1畜侦、add2柯?化版本都是我們手動(dòng)敲出來的元扔,下面看一下如何?動(dòng)將函數(shù)柯?化。
func add1(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
func add2(_ v1: Int, _ v2: Int, _ v3: Int) -> Int { v1 + v2 + v3 }
func currying<A, B, C>(_ fn: @escaping (A, B) -> C) -> (B) -> (A) -> C {
{ b in { a in fn(a, b) } }
}
func currying<A, B, C, D>(_ fn: @escaping (A, B, C) -> D) -> (C) -> (B) -> (A) -> D {
{ c in { b in { a in fn(a, b, c) } } }
}
let curriedAdd1 = currying(add1)
print(curriedAdd1(20)(10)) //10 + 20 = 30 20傳給b旋膳,10傳給a
let curriedAdd2 = currying(add2)
print(curriedAdd2(30)(20)(10)) //10 + 20 + 30 = 60 30傳給c澎语,20傳給b,10傳給a
我們也可以自定義運(yùn)算符验懊,將上面我們舉的例子的函數(shù)自動(dòng)柯?化:
func add(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
func sub(_ v1: Int, _ v2: Int) -> Int { v1 - v2 }
func multiple(_ v1: Int, _ v2: Int) -> Int { v1 * v2 }
func divide(_ v1: Int, _ v2: Int) -> Int { v1 / v2 }
func mod(_ v1: Int, _ v2: Int) -> Int { v1 % v2 }
//重載~運(yùn)算符咏连,將函數(shù)柯?化
prefix func ~<A, B, C>(_ fn: @escaping (A, B) -> C) -> (B) -> (A) -> C {
{ b in { a in fn(a, b) } }
}
//?定義>>>運(yùn)算符
infix operator >>> : AdditionPrecedence
func >>><A, B, C>(_ f1: @escaping (A) -> B,
_ f2: @escaping (B) -> C) -> (A) -> C {
{ f2(f1($0)) }
}
var num = 1
var fn = (~add)(3) >>> (~multiple)(5) >>> (~sub)(1) >>> (~mod)(10) >>> (~divide)(2)
fn(num) //[(num + 3) * 5 - 1] % 10 / 2
5. 函子(Functor)
① 什么是函子
怎么樣的Type才能稱之為函子(Functor)?如下:
func map<T>(_ fn: (Inner) -> T) -> Type<T>
支持如上map運(yùn)算的類型才能稱之為函子鲁森,可以發(fā)現(xiàn)有3個(gè)條件:
- 這個(gè)map運(yùn)算要支持泛型
- 要求接收一個(gè)函數(shù)祟滴,這個(gè)函數(shù)把Type內(nèi)部存放的數(shù)據(jù)當(dāng)作參數(shù)傳進(jìn)去,返回一個(gè)T
- 返回的也是同一種Type<T>類型
② Array歌溉、Optional為什么是函子
Array垄懂、Optional也是支持如上map運(yùn)算的類型,所以我們稱之為函子
Array的map定義:
// Array<Element>
public func map<T>(_ transform: (Element) -> T) -> Array<T>
Optional的map定義:
// Optional<Wrapped>
public func map<U>(_ transform: (Wrapped) -> U) -> Optional<U>
③ 圖解理解函子
如下是個(gè)函子痛垛,函子里面包裝的是2草慧,如果想對(duì)這個(gè)函子做+3的操作,怎么做呢匙头?
首先漫谷,將函子解包取出里面的2,再將2做+3的操作蹂析,得到5舔示,最后再將5又放到盒子里面,形成一個(gè)新的函子电抚,如下圖:
Optional的map就是這樣的惕稻,比如:想要對(duì)Optional(2)做+3的操作,首先將Optional(2)里面的2取出來做+3的操作蝙叛,得到5俺祠,最后又將5包裝成可選類型Optional(5),進(jìn)一步說明了Optional類型是函子借帘。
如果這個(gè)可選類型是空的蜘渣,那么map就不會(huì)調(diào)用,那么+3操作就不會(huì)執(zhí)行肺然,如下圖:
如果是數(shù)組蔫缸,里面存放的是2 4 6,先將2 4 6取出來分別做相應(yīng)的操作狰挡,最后操作的結(jié)果再包裝成數(shù)組捂龄,如下圖释涛。
6. 適用函子(Applicative Functor)
對(duì)任意一個(gè)函子F,如果能支持以下運(yùn)算倦沧,該函子就是一個(gè)適用函子
func pure<A>(_ value: A) -> F<A> //可以理解為唇撬,隨便給?個(gè)值就能返回??類型的泛型
func <*><A, B>(fn: F<(A) -> B>, value: F<A>) -> F<B> //可以理解為,給?個(gè)泛型F<A>和?個(gè)泛型函數(shù)fn展融,最后返回?個(gè)泛型B
① Optional是適用函子
Optional可以滿足上面兩個(gè)條件窖认,可以成為適用函子,如下:
func pure<A>(_ value: A) -> A? { value } //滿足了上面第一個(gè)條件
infix operator <*> : AdditionPrecedence
func <*><A, B>(fn: ((A) -> B)?, value: A?) -> B? { //滿足了上面第二個(gè)條件
guard let f = fn, let v = value else { return nil }
return f(v)
}
使用一下上面的運(yùn)算告希,如下:
var value: Int? = 10
var fn: ((Int) -> Int)? = { $0 * 2}
print(fn <*> value as Any) //Optional(20)
② 圖解適用函子
有操作如下:
函子只是將需要計(jì)算的值包裝到里面扑浸,適用函子會(huì)將需要計(jì)算的操作也包裝到里面,如下圖:
③ Array也是適用函子
Array也可以滿足上面兩個(gè)條件燕偶,也可以成為適用函子喝噪,如下:
func pure<A>(_ value: A) -> [A] { [value] }
func <*><A, B>(fn: [(A) -> B], value: [A]) -> [B] {
var arr: [B] = []
if fn.count == value.count {
for i in fn.startIndex..<fn.endIndex {
arr.append(fn[i](value[i]))
}
}
return arr
}
使用一下上面的運(yùn)算,如下:
print(pure(10)) // [10]
var arr = [{ $0 * 2}, { $0 + 10 }, { $0 - 5 }] <*> [1, 2, 3]
print(arr) // [2, 12, -2]
傳入?個(gè)數(shù)組A和?個(gè)函數(shù)數(shù)組指么,把數(shù)組A中每?個(gè)元素傳給函數(shù)數(shù)組中對(duì)應(yīng)的函數(shù)酝惧,這個(gè)函數(shù)拿到對(duì)應(yīng)的元素,計(jì)算后放到數(shù)組B里面伯诬,最后返回?cái)?shù)組B晚唇。
7. 單子(Monad)
對(duì)任意一個(gè)類型F,如果能支持以下運(yùn)算盗似,那么就可以稱為是一個(gè)單子(Monad)
func pure<A>(_ value: A) -> F<A>
func flatMap<A, B>(_ value: F<A>, _ fn: (A) -> F<B>) -> F<B>
很顯然哩陕,Array、Optional都是單子