Array
[TOC]
如何創(chuàng)建Array
定義空數(shù)組
var array1: Array<Int> = Array<Int>()
var array2: [Int] = []
var array3 = array2
定義空數(shù)組并指定初始值
//[3,3,3]
var threeInts = [Int](repeating: 3, count: 3)
//[3,3,3,3,3,3]
var sixInts = threeInts + threeInts
//[1,2,3,4,5]
var fiveInts = [1,2,3,4,5]
兩個(gè)常用的Array屬性
.count
獲取數(shù)組中元素的個(gè)數(shù)
.isEmpty
數(shù)組是否為空
訪問(wèn)Array中的元素
- 使用索引訪問(wèn)數(shù)組元素症昏,你必須確保索引的安全性长赞。如果索引超過(guò)了數(shù)組的范圍京办,程序就會(huì)崩潰
- 使用range operator訪問(wèn)數(shù)組的一個(gè)范圍
fiveInts[0...2] //[1,2,3]
fiveInts[0..<2] //[1,2]
用range operator得到的颅崩,并不是一個(gè)Array
,而是一個(gè)ArraySlice
,即Array
某一段內(nèi)容的View漠吻,它不真正保存數(shù)組的內(nèi)容,只保存這個(gè)view引用的數(shù)組的范圍
遍歷數(shù)組
除了訪問(wèn)單個(gè)元素外,另一類常用的需求就是順序訪問(wèn)數(shù)組中的每個(gè)成員艰管。在swift里,我們有三種基本的方法遍歷一個(gè)Array
- for循環(huán):
for value in fiveInts { print(value) }
- 如果我們想在遍歷的時(shí)候同時(shí)獲得索引和值蒋川,可以只用
enumerated()
方法牲芋,他會(huì)返回一個(gè)Sequence
對(duì)象,包含了每個(gè)成員的索引和值for (index, value) in fiveInts.enumerated() { print("\(index): \(value)") }
- 借助closure捺球,我們還可以使用
Array
對(duì)象的forEach
方法:fiveInts.forEach {print($0)}
添加和刪除元素
添加元素:
array1.append(1) //[1]
array1 += [2,3,4] //[1,2,3,4]
要在Array
中間位置添加元素缸浦,可以使用insert
方法:
//[1,2,3,4,5]
array1.insert(5, at: array1.endIndex)
刪除元素:
array1.remove(at: 4) //[1,2,3,4]
如果你想刪除最后一個(gè)元素,可以使用removeLast()
方法
array1.removeLast() //[1,2,3]
array2.removeLast() //This will crash!!!
理解Array和NSArray的差異
同樣是數(shù)組類型氮兵,Swift中Array
和Foundation 中的NSArray
有著截然不同的語(yǔ)義和用法裂逐。
按值語(yǔ)義實(shí)現(xiàn)的Array
在Swift中,Array
是按照值語(yǔ)義實(shí)現(xiàn)的泣栈,當(dāng)我們復(fù)制一個(gè)Array對(duì)象時(shí)卜高,會(huì)拷貝整個(gè)Array
的內(nèi)容
var a = [1,2,3] // [1,2,3]
let copyA = a // [1,2,3]
a.append(4)
// a [1,2,3,4]
// copyA [1,2,3]
// copyA.append(4) Compile error
根據(jù)上面的代碼,有兩點(diǎn)值得說(shuō)明:
- Swift 數(shù)組是否可以被修改完全是通過(guò)
var
和let
關(guān)鍵字決定的南片,Array
類型自身并不解決它是否可以被修改的問(wèn)題 - 復(fù)制
a
并向a
添加內(nèi)容之后掺涛,copyA
的內(nèi)容并不會(huì)修改。但是疼进,Swift在復(fù)制Array
時(shí)鸽照,同樣對(duì)Array
的性能有所考量,他是用了copy on write的方式颠悬。如果你僅僅復(fù)制了Array
而不對(duì)它修改時(shí)矮燎,真正的復(fù)制是不會(huì)發(fā)生的,兩個(gè)數(shù)組仍舊引用同一個(gè)內(nèi)存地址赔癌。只有當(dāng)你修改了其中一個(gè)Array
的內(nèi)容時(shí)诞外,才會(huì)真正讓兩個(gè)Array
對(duì)象分開(kāi)。為了看到這個(gè)過(guò)程灾票,我們先來(lái)實(shí)現(xiàn)一個(gè)方法峡谊,把保存Array
內(nèi)容的地址變成了一個(gè)字符串:
func getBufferAddress<T>(of array: [T]) -> String {
return array.withUnsafeBufferPointer {buffer in
return String(describing: buffer.baseAddress)
}
}
其中,withUnsafeBufferPointer
是Array
的一個(gè)方法刊苍,它可以把保存Array
內(nèi)容的地址既们,傳遞給它的closure參數(shù)。在我們的例子里正什,這個(gè)closure只是把Array
的地址啥纸,變成了一個(gè)String
對(duì)象。
getBufferAddress(of: a)
getBufferAddress(of: copyA)
a.append(4)
getBufferAddress(of: a)
getBufferAddress(of: copyA)
在我們運(yùn)行之后會(huì)發(fā)現(xiàn)婴氮,只有在給a
添加內(nèi)容后斯棒,它才被重新分配了內(nèi)存地址盾致。
按引用語(yǔ)義實(shí)現(xiàn)的NSArray
在Foundation中,數(shù)組這個(gè)類型有兩點(diǎn)和SwiftArray
是不同的:
- 數(shù)組是否可以被修改時(shí)通過(guò)NSArray和NSMutableArray這兩個(gè)類型來(lái)決定的
-
NSArray
和NSMutableArray
都是類對(duì)象荣暮,復(fù)制他們執(zhí)行的是引用語(yǔ)義
// Mutable array [1,2,3]
let b = NSMutableArray(array: [1,2,3])
// Const array [1,2,3]
let copyB: NSArray = b
// [0,1,2,3]
b.insert(0, at: 0)
// [0,1,2,3]
copyB
在上面的代碼中可以看出庭惜,盡管我們?cè)趧?chuàng)建copyB時(shí),使用了NSArray
穗酥,表明我們不希望它的值被修改护赊,由于這個(gè)賦值執(zhí)行的是應(yīng)用拷貝,因此砾跃,實(shí)際上它和b
指向的是同一塊內(nèi)存空間骏啰。因此,當(dāng)我們修改b
的內(nèi)容時(shí)蜓席,copyB
也就間接受到了影響器一。
為了在拷貝NSArray
對(duì)象時(shí),執(zhí)行值語(yǔ)義厨内,我們必須使用它的copy
方法復(fù)制所有的元素:
let b = NSMutableArray(array: [1,2,3])
let copyB: NSArray = b
let deepCopyB = b.copy() as! NSArray
b.insert(0, at: 0) //[0,1,2,3]
copyB // [0,1,2,3]
deepCopyB // [1,2,3]
當(dāng)我們使用NSArray
和NSMutableArray
時(shí)祈秕,Swift中的var
和let
關(guān)鍵字就和數(shù)組是否可以被修改沒(méi)關(guān)系了。它們只控制對(duì)應(yīng)的變量是否可以被賦值成新的NSArray
或NSMutableArray
對(duì)象
用Swift的方式使用Array
絕大多數(shù)時(shí)候雏胃,其實(shí)你不需要[]
對(duì)于下標(biāo)訪問(wèn)數(shù)組元素這種老舊的形式请毛,Swift的開(kāi)發(fā)者應(yīng)該是不太喜歡的。不喜歡下標(biāo)操作符的理由是瞭亮,對(duì)于array[index]
這樣的訪問(wèn)方仿,甚至都沒(méi)有使用optional來(lái)保護(hù)越界的情況
let a = [1,2,3]
type(of: a[1]) //Int.type
a[1]
的類型是Int,而不是Optional<Int>,這說(shuō)明你必須小心翼翼的使用index來(lái)訪問(wèn)Array
中的元素统翩,一旦index的值不正確仙蚜,你就需要承擔(dān)運(yùn)行崩潰的嚴(yán)重后果
我們可以采用其他的手段來(lái)替代下標(biāo)訪問(wèn)數(shù)組元素,比如我們想訪問(wèn)數(shù)組中的每一個(gè)元素時(shí):
a.forEach { print($0) }
//or
for value in a {
}
當(dāng)我們要獲得數(shù)組中每一個(gè)元素的索引和值時(shí):
for (index,value) in a.enumerated() {}
當(dāng)我們要獲得數(shù)組中元素的位置時(shí)(例如查找等于1的元素的索引):
a.index( $0 == 1 )
index
會(huì)返回一個(gè)Option<Int>
,當(dāng)要查找的元素存在時(shí)厂汗,就返回該元素的索引委粉,否則,就返回nil
娶桦。
當(dāng)我們要過(guò)濾數(shù)組中的某些元素時(shí)(例如贾节,去掉所有偶數(shù))
a.filter { $0 % 2 == 0 }
話說(shuō)回來(lái),給[]添加optional保護(hù)也不能解決安全問(wèn)題衷畦,因?yàn)橐坏┠鉬orce unwrapping 一個(gè)optional栗涂,就有可能會(huì)帶來(lái)一連串的force unwrapping。這不僅看上去不美觀祈争,從代碼表現(xiàn)的含義上來(lái)說(shuō)斤程,既然已經(jīng)準(zhǔn)備好要為結(jié)果全權(quán)負(fù)責(zé)了。又何必要再讓你多執(zhí)行一不force unwrapping呢铛嘱。
一些安全周到的方法
和[]
的高風(fēng)險(xiǎn)形成鮮明對(duì)比的是暖释,對(duì)于那些可以生成優(yōu)秀代碼的方法袭厂,Swift則考慮的面面俱到墨吓。例如:
訪問(wèn)數(shù)組中第一個(gè)和最后一個(gè)元素的first
和last
屬性球匕,當(dāng)Array
為空時(shí),他們的值都是nil:
a.first //1
a.last //3
type(of: a.first) // Optional<Int>.Type
另外一個(gè)值得一提的是在Array
末尾刪除元素帖烘。Swift為這個(gè)動(dòng)作提供了兩個(gè)API:
-
removeLast
,你需要自行確保數(shù)組中有元素亮曹,否則會(huì)引發(fā)運(yùn)行時(shí)錯(cuò)誤 -
popLast
,如果數(shù)組為空,會(huì)返回nil
為什么要如此呢秘症? 一個(gè)通俗的解釋就是照卦,為了表意更清晰的代碼。
當(dāng)你基于Array
實(shí)現(xiàn)諸如棧這樣后入先出的數(shù)據(jù)結(jié)構(gòu)時(shí)乡摹,彈出一個(gè)元素并判斷是否為空是一個(gè)常規(guī)的操作役耕,所以popLast
返回了一個(gè)optional。而對(duì)于更一般的"刪除數(shù)組中最后一個(gè)元素"這樣的行為聪廉,Swift認(rèn)為這沒(méi)有任何更具體的使用場(chǎng)景瞬痘,你應(yīng)該自己對(duì)這樣的"低級(jí)錯(cuò)誤"負(fù)責(zé)。
通過(guò)closure參數(shù)化對(duì)數(shù)組元素的變形操作
當(dāng)你對(duì)Array
做一些處理的時(shí)候板熊,像C語(yǔ)言中類似的循環(huán)和下標(biāo)框全,都不是理想的選擇。Swift有一套自己的"現(xiàn)代化"手段干签。簡(jiǎn)單來(lái)說(shuō)津辩,就是通過(guò)closure來(lái)參數(shù)化對(duì)數(shù)組的操作行為。
從循環(huán)到map
假設(shè)我們有一個(gè)簡(jiǎn)單的Fibonacci序列:[0,1,1,2,3,5]
容劳。如果我們要計(jì)算每個(gè)元素的平方喘沿,怎么辦呢?
一個(gè)最樸實(shí)的做法是for
循環(huán):
var fibonacci = [0,1,1,2,3,5]
var squares = [Int]()
for value in fibonacci {
squares.append(value * value)
}
如果你覺(jué)得這還不是個(gè)足夠引起你注意的問(wèn)題竭贩,那么蚜印,當(dāng)我們要定義一個(gè)常量squares
的時(shí)候,上面的代碼就完全無(wú)法勝任了娶视。怎么辦呢晒哄?先看解決辦法:
//[0,1,1,4,9,25]
let constSquares = fibonacci.map { $0 * $0 }
上面的代碼,和之前的for
循環(huán)執(zhí)行的結(jié)果是相同的肪获。顯然寝凌,它比for
循環(huán)更具表現(xiàn)力,并且也能把我們期望的結(jié)果定義成常量孝赫。當(dāng)然较木,map
并不是什么魔法,無(wú)非就是把for
循環(huán)執(zhí)行的邏輯青柄,封裝在了函數(shù)里伐债,這樣我們就可以把函數(shù)的返回值賦值給常量了预侯,我們可以通過(guò)extension
很簡(jiǎn)單的自己來(lái)實(shí)現(xiàn)map
extension Array {
func myMap<T>(_ transform: (Element) -> T) -> [T] {
var tmp: [T] = []
tmp.reserveCapacity(count)
for value in self {
tmp.append(transform(value))
}
return tmp
}
}
雖然和Swift標(biāo)準(zhǔn)庫(kù)相比,MyMap
的實(shí)現(xiàn)中去掉了和異常聲明相關(guān)的部分峰锁。但它已經(jīng)足以表現(xiàn)map的核心實(shí)現(xiàn)過(guò)程了萎馅。除了在append之前使用了reserveCapacity
給新數(shù)組預(yù)留了空間之外,它的實(shí)現(xiàn)過(guò)程和一開(kāi)始我們使用的for
循環(huán)沒(méi)有任何差別.
如果你還不了解
Element
也沒(méi)關(guān)系虹蒋,把它理解為Array
中元素類型的替代符就好了再后面我們講到Sequence類型的時(shí)候糜芳,會(huì)專門(mén)提到它。
完成后魄衅,當(dāng)我們?cè)趐layground里測(cè)試的時(shí)候:
// [0,1,1,4,9,25]
let constSuquence1 = fibonacci.myMap { $0 * $0 }
就會(huì)發(fā)現(xiàn)執(zhí)行結(jié)果和之前的constSequence
是一樣的了峭竣。
參數(shù)化數(shù)組元素的執(zhí)行動(dòng)作
其實(shí),仔細(xì)觀察myMap
的實(shí)現(xiàn)晃虫,就會(huì)發(fā)現(xiàn)它最大的意義皆撩,就是保留了遍歷Array
的過(guò)程,而把執(zhí)行的動(dòng)作留給了myApp
的調(diào)用者通過(guò)參數(shù)去定制哲银。而這扛吞,就是我們一開(kāi)始提到的用closure來(lái)參數(shù)化對(duì)數(shù)組的操作行為的含義。
有了這樣的思路之后盘榨,我們就可以把各種常用的帶有遍歷行為的操作喻粹,定制成多種不同的遍歷"套路",而把對(duì)數(shù)組中每一個(gè)元素的處理動(dòng)作留給函數(shù)的調(diào)用者草巡。但是別急守呜,在開(kāi)始自動(dòng)動(dòng)手造輪子之前,Swift library已經(jīng)為我們準(zhǔn)備了一些山憨,例如:
首先查乒,是找到最小、最大值郁竟,對(duì)于這類操作來(lái)說(shuō)玛迄,只要數(shù)組中的元素實(shí)現(xiàn)了"Equatable"protocol,我們甚至無(wú)需定義對(duì)元素的具體操作:
fibonacci.min() // 0
fibonacci.max() // 5
使用min
和max
很安全棚亩,因?yàn)楫?dāng)數(shù)組為空時(shí)蓖议,這兩個(gè)方法將返回nil
.
其次,過(guò)濾出滿足特定條件的元素讥蟆,我們只要通過(guò)參數(shù)制定篩選規(guī)則就好了:
fibonacci.filter { $0 % 2 == 0 }
第三勒虾,比較數(shù)組相等或以特定元素開(kāi)始。對(duì)這類操作瘸彤,我們需要提供兩個(gè)內(nèi)容修然,一個(gè)是要比較的數(shù)組,另一個(gè)則是比較的規(guī)則:
//false
fibonacci.elementsEqual([0,1,1], by: { $0 == $1 })
//true
fibonacci.starts(with: [0,1,1], by: { $0 == $1 })
第四,最原始的for
循環(huán)的替代品:
fibonacci.forEach { print($0) }
// 0
// 1
// ...
要注意它和map
的一個(gè)重要區(qū)別:forEach
并不處理closure參數(shù)的返回值愕宋。因此它只適合用來(lái)對(duì)數(shù)組中的元素進(jìn)行一些操作玻靡,而不能用來(lái)產(chǎn)生返回結(jié)果。
第五中贝,對(duì)數(shù)組進(jìn)行排序囤捻,這時(shí),我們需要通過(guò)參數(shù)制定的是排序規(guī)則:
// [0,1,1,2,3,5]
fibonacci.sorted()
// [5,3,2,1,1,0]
fibonacci.sorted(by: >)
let privot = fibonacci.partition(by: { $0 < 1 })
fibonacci[0 ..< privot] // [1,1,2,3,5]
fibonacci[privot ..< fibonacci.endIndex] //[0]
其中 sorted(by:)
的用法是很直接的雄妥,它默認(rèn)采用升序排列最蕾。同事依溯,也允許我們通過(guò)by
自定義排序規(guī)則老厌。在這里>
是 { $0 > $1 }
的簡(jiǎn)寫(xiě)形式。Swift中很多在不影響語(yǔ)義的情況下的簡(jiǎn)寫(xiě)形式黎炉。
而partition(by:)
則會(huì)先對(duì)傳遞給它的數(shù)組進(jìn)行重排枝秤,然后根據(jù)指定的條件在重排的結(jié)果中返回一個(gè)分界點(diǎn)位置。這個(gè)分界點(diǎn)分開(kāi)的兩部分中慷嗜,前半部分的元素都不滿足指定條件淀弹;后半部分都滿足指定條件。而后庆械,我們就可以使用range operator來(lái)訪問(wèn)者兩個(gè)區(qū)間形成的Array
對(duì)象薇溃。大家可以根據(jù)例子中注釋的結(jié)果,來(lái)理解partition的用法缭乘。
第六沐序,是把數(shù)組中的所有內(nèi)容,"合并"成某種形式的值堕绩,對(duì)這類操作策幼,我們需要指定的,是合并前的初始值奴紧,以及"合并"的規(guī)則特姐。例如,我們計(jì)算fibonacci
中所有元素的和:
fibonacci.reduce(0, +) //12
在這里黍氮,初始值為0唐含,和第二個(gè)參數(shù)+
,則是 { $0 + $1 }
的縮寫(xiě).
通過(guò)這些例子,你應(yīng)該能感覺(jué)到了沫浆,這些通過(guò)各種形式封裝了遍歷動(dòng)作的方法捷枯,他們之中的任何一個(gè),都比直接通過(guò)for
循環(huán)實(shí)現(xiàn)具有更強(qiáng)的表現(xiàn)力件缸。這些API铜靶,開(kāi)始讓我們的代碼從面向機(jī)器的,轉(zhuǎn)變成面向業(yè)務(wù)需求的。因此争剿,在Swift里已艰,你應(yīng)該試著讓自己轉(zhuǎn)變觀念,當(dāng)你面對(duì)一個(gè)Array
時(shí)蚕苇,你真的幾乎可以忘記下標(biāo)和循環(huán)了哩掺。
區(qū)分修改外部變量和保存內(nèi)部狀態(tài)
當(dāng)我們使用上面提到的這些帶有closure參數(shù)的Array
方法時(shí),一個(gè)不好的做法就是通過(guò)closure去修改外部變量涩笤,并依賴這種副作用產(chǎn)生的結(jié)果嚼吞。來(lái)看一個(gè)例子:
var sum = 0
let constSquare2 = fibonacci.map { (fib: Int) -> Int in
sum += fib
return fib * fib
}
在這個(gè)例子里,map
的執(zhí)行產(chǎn)生了一個(gè)副作用蹬碧,就是對(duì)fibonacci
中所有的元素求和舱禽。這不是一個(gè)好的方法,我們應(yīng)該避免這樣恩沽。你應(yīng)該單獨(dú)使用reduce
來(lái)完成這個(gè)操作誊稚,或者如果一定要在closure參數(shù)里修改外部變量,哪怕用forEach
也比map
更好的方案罗心。
但是里伯,在函數(shù)實(shí)現(xiàn)內(nèi)部,專門(mén)用一個(gè)外部變量來(lái)保存closure參數(shù)的執(zhí)行狀態(tài)渤闷,則是一個(gè)常用的實(shí)現(xiàn)技法疾瓮。例如,我們要?jiǎng)?chuàng)建一個(gè)新的數(shù)組飒箭,其中每個(gè)值狼电,都是數(shù)組當(dāng)前位置和之前所有元素的和,可以這樣:
extension Array {
func accumulate<T>(_ initial: T, _ nextSum: (T, Element) -> T) -> [T] {
var sum = initial
return map { next in
sum = nextSum(sum, next)
return sum
}
}
}
在上面這個(gè)例子里补憾,我們利用map
的closure參數(shù)捕獲了sum
漫萄,這樣就保存了每一次執(zhí)行map
時(shí),之前所有元素的和盈匾。
//[0,1,2,4,7,12]
fibonacci.accumulate(0, +)
Filter/Reduce/FlatMap的實(shí)現(xiàn)和拓展
這一章著重了解三個(gè)比較重要的Array
API,filter
/reduce
/flatMap
,它們和我們?cè)谏弦还?jié)中實(shí)現(xiàn)的map一起腾务,形成了各種Array
操作的基礎(chǔ)。
filter和filter類似的語(yǔ)義
之前削饵,我們提到過(guò)filter
的用法岩瘦,用于在Array
中,過(guò)濾滿足特定條件的元素窿撬。而這個(gè)條件启昧,就是通過(guò)filter
的closure參數(shù)來(lái)確定的:
var fibonacci = [0,1,1,2,3,5]
//[0,2]
fibonacci.filter { %0 % 2 == 0}
按照上一節(jié)中實(shí)現(xiàn)的map
的思路,我們可以自己來(lái)實(shí)現(xiàn)一個(gè)filter
extension Array {
func myFilter(_ predicate: (Element) -> Bool) -> [Element] {
var temp: [Element] = []
for value in self where predicate(value) {
temp.append(value)
}
return temp
}
}
在上面的實(shí)現(xiàn)里劈伴,最核心的環(huán)節(jié)就是通過(guò)where
條件的for
循環(huán)找到原數(shù)組中符合條件的元素密末,然后把它們一一添加到temp
中,并最終返回給函數(shù)的調(diào)用者。然后严里,我們測(cè)試下myFilter
fibonacci.myFilter { $0 % 2 == 0 } // [0,2]
結(jié)果應(yīng)該是和標(biāo)準(zhǔn)庫(kù)中的自帶的filter是一樣的新啼。理解filter
之后,我們就可以自行定義一些標(biāo)準(zhǔn)庫(kù)中沒(méi)有的方法刹碾。例如:
剔除掉數(shù)組中滿足條件的元素:
extension Array {
func reject(_ predicate: (Element) -> Bool) -> [Element]{
return filter { !predicate($0) }
}
}
我們只要把調(diào)用轉(zhuǎn)發(fā)給filter
,然后把指定的條件取反就好了燥撞。這樣,提出元素的代碼語(yǔ)義上就會(huì)更好看一些:
fibonacci.reject { $0 % 2 == 0 } //[1,1,3,5]
另一個(gè)基于filter
語(yǔ)義的常用操作是判斷數(shù)組中是否存在滿足條件的元素迷帜。下面的代碼可以完成任務(wù):
fibonacci.filter { $0 % 2 == 0}.count > 0 //true
但這樣做在性能上并不理想物舒,因?yàn)榧幢阏业搅藵M足條件的元素,也要遍歷完整個(gè)數(shù)組戏锹,這顯然是沒(méi)有必要的冠胯。Swift標(biāo)準(zhǔn)庫(kù)中,提供了一個(gè)更方便的方法:
fibonacci.containts { $0 % 2 == 0} //true
contains
的一個(gè)好處就是只要遇到滿足條件的元素景用,函數(shù)的執(zhí)行就終止了涵叮。基于這個(gè)contains
伞插,我們還可以給Array
添加一個(gè)新的方法,用來(lái)判斷Array
中所有的元素是否滿足特定的條件
extension Array {
func allMatch(_ predicate: (Element) -> Bool) -> Bool{
return !contains { !predicate:$0 }
}
}
在allMatch
的實(shí)現(xiàn)里盾碗,只要沒(méi)有不滿足條件的元素媚污,也就是所有的元素都滿足條件了。我們可以用下面的代碼測(cè)試一下:
let events = [2,4,6,8]
events.allMatch { $0 % 2 == 0 } // true
reduce和reduce相關(guān)的語(yǔ)義
除了用一個(gè)數(shù)組生成一個(gè)新的數(shù)組廷雅,有時(shí)耗美,我們會(huì)希望把一個(gè)數(shù)組變成某種形式的值。例如,之前我們提到的求和:
fibonacci.reduce(0, +) // 12
了解reduce的進(jìn)一步用法之前航缀,我們先自己實(shí)現(xiàn)一個(gè)
extension Array {
func myReduce<T>(_ initial: T, _ next: (T, Element) -> T) -> T {
var temp = initial
for value in self {
temp = next(temp,value)
}
return temp
}
}
從上面可以看出商架,reduce
的實(shí)現(xiàn)也沒(méi)有什么神奇之處。無(wú)非就是把for
循環(huán)迭代相加的過(guò)程給封裝了起來(lái)芥玉。然后蛇摸,用下面的代碼測(cè)試一下,就會(huì)發(fā)現(xiàn)和標(biāo)準(zhǔn)庫(kù)里的reduce一樣了灿巧。
fibonacci.myReduce(0,+) //12
除了求和以外赶袄,我們還可以把fibonacci
reduce成一個(gè)字符串
let str = fibonacci.myReduce("") { str, num in
return str + "\(num)"
}
// "0 1 1 2 3 5"
甚至,我們還可以用reduce
模擬map
和filter
的實(shí)現(xiàn):
extension Array {
func myMap2<T>(_ transform: (Element) -> T) -> [T] {
return reduce([],{ $0 + transform($1) })
}
func myFilter2(_ predicate: (Element) -> Bool) -> [ELement] {
return reduce([],{ predicate($1) ? $0 + [$1] : $0 })
}
}
然后簡(jiǎn)單測(cè)試一下:
//[0,1,1,4,9,25]
fibonacci.myMap2 { $0 * $0 }
//[0,2]
fibonacci.myFilter2 { $0 % 2 == 0 }
他們的結(jié)果和標(biāo)準(zhǔn)庫(kù)中的map
和 filter是一樣的抠藕。但是饿肺,這種看似優(yōu)雅的寫(xiě)法卻沒(méi)有想象中的那么好。在他們內(nèi)部的reduce
調(diào)用中盾似,每一次$0
的參數(shù)都是一個(gè)新建的數(shù)組敬辣,因此整個(gè)算法的復(fù)雜度是O(n^2),而不是for
循環(huán)版本的O(n)。所以溉跃,這樣的實(shí)現(xiàn)方法最好還是用來(lái)作為理解reduce
用法的例子
后面會(huì)陸續(xù)給出我在Swift學(xué)習(xí)中的筆記汰聋。如果有什么錯(cuò)誤,請(qǐng)?jiān)谙路皆u(píng)論處給出喊积,我會(huì)及時(shí)進(jìn)行修改烹困。