Swift
語言提供 Arrays
潭陪、Sets
和 Dictionaries
三種基本的集合類型用來存儲集合數(shù)據(jù)。數(shù)組(Arrays)是有序數(shù)據(jù)的集最蕾。集合(Sets)是無序無重復數(shù)據(jù)的集依溯。字典(Dictionaries)是無序的鍵值對的集。
Swift 語言中的 Arrays
瘟则、Sets
和 Dictionaries
中存儲的數(shù)據(jù)值類型必須明確黎炉。這意味著我們不能把錯誤的數(shù)據(jù)類型插入其中。同時這也說明你完全可以對取回值的類型非常放心醋拧。
注意: Swift 的
Arrays
慷嗜、Sets
和Dictionaries
類型被實現(xiàn)為泛型集合
數(shù)組
數(shù)組是Swift中最普通的集合,數(shù)組是有序的容器丹壕,并且容器中的每一個元素都是相同的類型庆械, 我們可以使用下標對其直接進行訪問(又稱為隨機訪問),相同的值可以多次出現(xiàn)在一個數(shù)組的不同位置中。
注意:
Swift
的Array
類型被橋接到Foundation
中的NSArray
類菌赖。更多關(guān)于在Foundation
和Cocoa
中使用Array
的信息缭乘,參見 Using Swift with Cocoa and Obejective-C(Swift 4.1) 中使用 Cocoa 數(shù)據(jù)類型部分。
數(shù)組的簡單語法
public struct Array<Element> { }
寫Swift數(shù)組應該遵循像Array<Element>
這樣的形式琉用,其中Element
是這個數(shù)組中唯一允許存在的數(shù)據(jù)類型堕绩。我們也可以使用像[Element]
這樣的簡單語法。盡管兩種形式在功能上是一樣的邑时,但是推薦[Element]
寫法奴紧,Element
是一個泛型表示可指定任意類型。
創(chuàng)建一個空數(shù)組
我們可以使用構(gòu)造語法來創(chuàng)建一個由特定數(shù)據(jù)類型構(gòu)成的空數(shù)組:
var someInts = [Int]()
print("someInts is of type [Int] with \(someInts.count) items.”)
//打印 "someInts is of type [Int] with 0 items.”
注意晶丘,通過構(gòu)造函數(shù)的類型黍氮,someInts
的值類型被推斷為[Int]
。
如果代碼上下文中已經(jīng)提供了類型信息,例如一個函數(shù)參數(shù)或者一個已經(jīng)定義好類型的常量或者變量沫浆,那么我們可以使用空數(shù)組語句創(chuàng)建一個空數(shù)組觉壶,它的寫法很簡單:[]
(一對空方括號):
someInts.append(3) //someInts 現(xiàn)在包含一個 Int 值,值為3
someInts = [] //someInts 現(xiàn)在是空數(shù)組件缸,但是仍然是 [Int] 類型的。
創(chuàng)建一個帶有默認值的數(shù)組
Swift
中的Array
類型還提供一個可以創(chuàng)建特定大小并且所有數(shù)據(jù)都被默認的構(gòu)造方法叔遂。我們可以把準備加入新數(shù)組的數(shù)據(jù)項數(shù)量(count)
和適當類型的初始值(repeating)
傳入數(shù)組構(gòu)造函數(shù):
var threeDoubles = Array(repeating: 0.0, count: 3)
//threeDoubles 是一種 [Double] 數(shù)組他炊,等價于 [0.0, 0.0, 0.0]
通過兩個數(shù)組相加創(chuàng)建一個數(shù)組
我們可以使用加法操作符(+)
來組合兩種已存在的相同類型數(shù)組。新數(shù)組的數(shù)據(jù)類型會被從兩個數(shù)組的數(shù)據(jù)類型中推斷出來:
var anotherThreeDoubles = Array(repeating: 2.5, count: 3)
//anotherThreeDoubles 被推斷為 [Double]已艰,等價于 [2.5, 2.5, 2.5]
var sixDoubles = threeDoubles + anotherThreeDoubles
//sixDoubles 被推斷為 [Double]痊末,等價于 [0.0, 0.0, 0.0, 2.5, 2.5, 2.5]
用數(shù)組字面量構(gòu)造數(shù)組
我們可以使用數(shù)組字面量來進行數(shù)組構(gòu)造,這是一種用一個或者多個數(shù)值構(gòu)造數(shù)組的簡單方法哩掺。數(shù)組字面量是一系列由逗號分割并由方括號包含的數(shù)值:形如:[value 1, value 2, value 3]
凿叠。下面這個例子創(chuàng)建了一個叫做shoppingList
并且存儲String
的數(shù)組:
var shoppingList: [String] = ["Eggs", "Milk"]
shoppingList
已經(jīng)被構(gòu)造并且擁有兩個初始項。shoppingList
變量被聲明為“字符串值類型的數(shù)組“嚼吞,記作[String]
盒件。 因為這個數(shù)組被規(guī)定只有String
一種數(shù)據(jù)結(jié)構(gòu),所以只有String
類型可以在其中被存取舱禽。 在這里炒刁,shoppingList
數(shù)組由兩個String
值("Eggs" 和"Milk")構(gòu)造,并且由數(shù)組字面量定義誊稚。
注意:shoppingList
數(shù)組被聲明為變量(var
關(guān)鍵字創(chuàng)建)而不是常量(let
創(chuàng)建)是因為以后可能會有更多的數(shù)據(jù)項被插入其中翔始。
在這個例子中,字面量僅僅包含兩個String
值里伯。匹配了該數(shù)組的變量聲明(只能包含String
的數(shù)組)城瞎,所以這個字面量的分配過程可以作為用兩個初始項來構(gòu)造shoppingList
的一種方式。
由于Swift
的類型推斷機制疾瓮,當我們用字面量構(gòu)造只擁有相同類型值數(shù)組的時候脖镀,我們不必把數(shù)組的類型定義清楚。shoppingList
的構(gòu)造也可以這樣寫:
var shoppingList = ["Eggs", "Milk"]
因為所有數(shù)組字面量中的值都是相同的類型爷贫,Swift
可以推斷出[String]
是shoppingList
中變量的正確類型认然。
訪問和修改數(shù)組
我們可以通過數(shù)組的方法和屬性來訪問和修改數(shù)組,或者使用下標語法漫萄,可以使用數(shù)組的只讀屬性count
來獲取數(shù)組中的數(shù)據(jù)項數(shù)量:
print("The shopping list contains \(shoppingList.count) items.")
// 輸出 "The shopping list contains 2 items."
使用布爾屬性isEmpty
作為一個縮寫形式去檢查count
屬性是否為0:
if shoppingList.isEmpty {
print("The shopping list is empty.")
} else {
print("The shopping list is not empty.")
}
// 打印 "The shopping list is not empty."
也可以使用append()
方法在數(shù)組后面添加新的數(shù)據(jù)項:
shoppingList.append("Flour")
// shoppingList 現(xiàn)在有3個數(shù)據(jù)項
除此之外卷员,使用加法賦值運算符(+=)
也可以直接在數(shù)組后面添加一個或多個擁有相同類型的數(shù)據(jù)項:
shoppingList += ["Baking Powder"]
// shoppingList 現(xiàn)在有四項了
shoppingList += ["Chocolate Spread", "Cheese", "Butter"]
// shoppingList 現(xiàn)在有七項了
可以直接使用下標語法來獲取數(shù)組中的數(shù)據(jù)項,把我們需要的數(shù)據(jù)項的索引值放在直接放在數(shù)組名稱的方括號中:
var firstItem = shoppingList[0]// 第一項是 "Eggs"
注意:第一項在數(shù)組中的索引值是0而不是1腾务。 Swift 中的數(shù)組索引總是從零開始毕骡。
我們也可以用下標來改變某個已有索引值對應的數(shù)據(jù)值:
shoppingList[0] = "Six eggs"
// 其中的第一項現(xiàn)在是 "Six eggs" 而不是 “Eggs"
還可以利用下標來一次改變一系列數(shù)據(jù)值,即使新數(shù)據(jù)和原有數(shù)據(jù)的數(shù)量是不一樣的。下面的例子把"Chocolate Spread"未巫,"Cheese"窿撬,和"Butter"替換為"Bananas"和 "Apples":
shoppingList[4...6] = ["Bananas", "Apples"]// shoppingList 現(xiàn)在有6項
注意:不可以用下標訪問的形式去在數(shù)組尾部添加新項。
數(shù)組的插入
調(diào)用數(shù)組的insert(_:at:)
方法來在某個具體索引值之前添加數(shù)據(jù)項:
shoppingList.insert("Maple Syrup", at: 0)
// shoppingList 現(xiàn)在有7項, "Maple Syrup" 現(xiàn)在是這個列表中的第一項
這次insert(_:at:)
方法調(diào)用把值為"Maple Syrup"
的新數(shù)據(jù)項插入列表的最開始位置叙凡,并且使用0作為索引值劈伴。
數(shù)組的刪除
類似的我們可以使用remove(at:)
方法來移除數(shù)組中的某一項。這個方法把數(shù)組在特定索引值中存儲的數(shù)據(jù)項移除并且返回這個被移除的數(shù)據(jù)項(我們不需要的時候就可以無視它):
shoppingList.remove(at: 0)
// 索引值為0的數(shù)據(jù)項被移除
// shoppingList現(xiàn)在只有6項握爷,而且不包括Maple Syrup;
// mapleSyrup常量的值等于被移除數(shù)據(jù)項的值 "Maple Syrup"
注意:如果我們試著對索引越界的數(shù)據(jù)進行檢索或者設置新值的操作跛璧,會引發(fā)一個運行期錯誤。
我們可以使用索引值和數(shù)組的count屬性進行比較來在使用某個索引之前先檢驗是否有效新啼。除了當count
等于0
時(說明這是個空數(shù)組)追城,最大索引值一直是count - 1
,因為數(shù)組都是零起索引燥撞。
數(shù)據(jù)項被移除后數(shù)組中的空出項會被自動填補座柱,所以現(xiàn)在索引值為0的數(shù)據(jù)項的值再次等于"Six eggs":
firstItem = shoppingList[0]
// firstItem 現(xiàn)在等于"Six eggs"
如果我們只想把數(shù)組中的最后一項移除,可以使用removeLast()
方法而不是remove(at:)
方法來避免我們需要獲取數(shù)組的count屬性物舒。就像后者一樣色洞,前者也會返回被移除的數(shù)據(jù)項:
let apples = shoppingList.removeLast()
// 數(shù)組的最后一項被移除了
// shoppingList現(xiàn)在只有5項,不包括Apples
// apples常量的值現(xiàn)在等于 "Apples" 字符串
數(shù)組的遍歷
我們可以使用for-in
循環(huán)來遍歷所有數(shù)組中的數(shù)據(jù)項:
for item in shoppingList {
print(item)
}
// Six eggs
// Milk
// Flour
// Baking Powder
// Bananas
如果我們同時需要每個數(shù)據(jù)項的值和索引值茶鉴,可以使用enumerated()
方法來進行數(shù)組遍歷锋玲。enumerated()
返回一個由每一個數(shù)據(jù)項索引值和數(shù)據(jù)值組成的元組。我們可以把這個元組分解成臨時常量或者變量來進行遍歷:
for (index, value) in shoppingList.enumerated() {
print("Item \(String(index + 1)): \(value)")
}
// Item 1: Six eggs
// Item 2: Milk
// Item 3: Flour
// Item 4: Baking Powder
// Item 5: Bananas
遍歷數(shù)組涵叮,但是不包括第一個元素
for x in shoppingList.dropFirst(){
print(x) //
}
/*
Milk
Flour
Baking Powder
Bananas
*/
遍歷數(shù)組惭蹂,但是不包括最后一個或者幾個元素
for x in shoppingList.dropLast(){
print(x)
}
/*
Six eggs
Milk
Flour
Baking Powder
*/
for x in shoppingList.dropLast(3){
print(x)
}
/*
Six eggs
Milk
*/
數(shù)組的可變性
舉個例子,要創(chuàng)建一個數(shù)字的數(shù)組割粮,我們可以這么寫:
// 斐波拉契數(shù)列
let fibs = [0, 1, 1, 2, 3, 5]
如果我們使用像append
這樣的方法來修改上面定義的數(shù)組盾碗,會得到編譯錯誤。因為在上面的代碼中數(shù)組是用let
生命為常量的舀瓢。在很大情景下廷雅,這是正常的做法,它可以避免我們不小心對數(shù)組作出改變京髓。如果我們想按照變量的方式來使用數(shù)組航缀,我們需要使用var
來進行定義,而且很容易添加單個或者一系列元素
var mutableFibs = [0, 1, 1, 2, 3, 5]
mutableFibs.append(8)
mutableFibs.append(contentsOf: [13,21,34])
區(qū)別使用va
r和let
可以給我們帶來不少好處堰怨,使用let
定義的變量因為其具有不變性芥玉,因此更有理由被優(yōu)先使用,當你讀到類似let fib=...
這樣的聲明時备图,你可以確定fibs
的值將永遠不變灿巧,這一點是由編譯器強制保證的赶袄。
不過,要注意這只針對那些具有值語義的類型抠藕。使用let定義的類實例對象(也就是說對于引用類型)時饿肺,它保證的是這個引用永遠不會發(fā)生變化,你不能再給這個引用賦一個新的值,但是這個引用所指向的對象卻是可以改變的。
數(shù)組是值類型
數(shù)組和標準庫中的所有集合類型一樣达传,是具有值語義的。當你創(chuàng)建一個新的數(shù)組變量并且把一個已經(jīng)存在的數(shù)組賦值給它時购岗,這個數(shù)組的內(nèi)容會被賦值。
舉個例子门粪,在下面的代碼中,x將不會被更改:
var x = [1,2,3]
var y = x
y.append(4)
y // 1,2,3,4
x // 1,2,3
var y=x
語句復制了x
烹困,所以在4
添加到y
末尾的時候玄妈,x
并不會發(fā)生改變,它的值依然是[1,2,3]
髓梅。當你把數(shù)組傳遞給一個函數(shù)時拟蜻,會發(fā)生同樣的事情;方法將得到這個數(shù)組的一份本地復制枯饿,所有對它的改變都不會影響調(diào)用者所持有的數(shù)組
對比一下Foundation
框架中NSArray
在可變特性上的處理方法酝锅,NSArray
中沒有更改方法,想要更改一個數(shù)組奢方,你必須使用NSMutableArray
搔扁。但是,就算你擁有的是一個不可變的NSArray
蟋字,但是它的引用特性并不能保證這個數(shù)組不會被改變稿蹲。
let a = NSMutableArray(array: [1,2,3])
let b: NSArray = a // 不想讓b發(fā)生改變
a.insert(4, at: 3) // 但是事實上b的改變依然能夠被a影響
b // 1,2,3,4
正確的方式是在賦值時,先收到進行復制
let c = NSMutableArray(array: [1,2,3])
let b: NSArray = c.copy() as! NSArray // 不想讓b發(fā)生改變
c.insert(4, at: 3) // 1,2,3,4
b // 1,2,3
該例子中顯而易見鹊奖,我們需要進行復制苛聘,因為a
的聲明畢竟是可變的,但是當把數(shù)組在方法和函數(shù)之間來回傳遞的時候忠聚,事情可能就不那么明顯了设哗。
而在Swift
中,相較于NSArray
和NSMutableArray
兩種類型两蟀,數(shù)組只有嚴重統(tǒng)一的類型网梢,那就是Array
。使用var
可以將數(shù)組定義為可變垫竞,但是區(qū)別與NS
的數(shù)組澎粟,當你使用let
定義第二個數(shù)組,并將第一個數(shù)組賦值給它徐裸,也可以保證這個新的數(shù)組是不會改變的,因為這里沒有公用的引用
創(chuàng)建如此多的復制有可能造成性能問題气笙,不過實際上Swift
標準庫中的所有集合類型都使用了“寫時復制”這一技術(shù),它能夠保證只在必要的時候?qū)?shù)據(jù)進行復制谭期。在上面的例子中,y.append被調(diào)用之前胀瞪,x和y都將共享內(nèi)部的存儲
Swift數(shù)組提供了你能想到的所有常規(guī)操作,像是isEmpty
或是count
幔摸。數(shù)組也允許直接使用特定的下標直接訪問其中的元素,像是fib[3]
患雇。不過要注意:在使用下標獲取元素之前,需要確保索引值沒有超出范圍翠储,否則會導致程序奔潰
數(shù)組變形
對數(shù)組中的每個值指向轉(zhuǎn)換是一個很常見的任務庐舟。常用操作:創(chuàng)建一個新數(shù)組,對已有數(shù)組中的元素進行循環(huán)依次取出其中元素杠娱,對取出的元素進行操作,并把操作的結(jié)果加入到新數(shù)組的末尾睹簇。比如:下面的代碼計算了一個整型數(shù)組里元素的平方
var squared: [Int] = []
for fib in fibs {
squared.append(fib * fib)
}
squared // 0,1,1,4,9,25
Swift
數(shù)組擁有map
方法疲憋,這個方法來自函數(shù)式編程的世界缚柳,專門用于對數(shù)組中的元素進行遍歷操作
public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]
map
方法接受一個閉包作為參數(shù)构舟, 然后它會遍歷整個數(shù)組狗超,并對數(shù)組中每一個元素執(zhí)行閉包中定義的操作苦蒿。 相當于對數(shù)組中的所有元素做了一個映射,這是非常普遍的操作
let squared = fibs.map { $0 * $0}
squared // 0,1,1,4,9,25
使用map函數(shù)的優(yōu)勢
代碼很短灸姊,代碼長度短意味著錯誤少,不過更重要的是夯膀,它比原來更清晰了。所有無關(guān)的內(nèi)容都被移除了,一旦習慣了
map
的使用押袍,就會發(fā)現(xiàn)map
就像一個信號侮东,一旦看見它驱敲,就會知道即將有一個函數(shù)被作用在數(shù)組的每一個元素上,并返回一個數(shù)組,它將包含所有被轉(zhuǎn)換后的結(jié)果squared
將由map
的結(jié)果得到姚建,我們不會再改變它的值掸冤,所以也就不再需要用var
來進行聲明了铅匹,我們可以將其聲明為let
罗丰。另外,由于數(shù)組元素的類型可以從傳遞給map
的函數(shù)中推斷出來再姑,我們也不再需要為squared
顯示地指明類型了創(chuàng)建
map
函數(shù)并不難萌抵,只需要把for循環(huán)模版部分用一個泛型函數(shù)封裝起來就可以了,下面是一種可能的實現(xiàn)方式
extension Array {
func map<T>(_ transform: (Element) -> T) -> [T] {
var result: [T] = []
result.reserveCapacity(count)
for x in self {
result.append(transform(x))
}
return result
}
}
Element
是數(shù)組包含元素類型的占位符元镀,T
是元素轉(zhuǎn)換之后的類型占位符绍填。map
函數(shù)本身并不關(guān)心
Element
和T
究竟是什么,它們可以是任意類型。T
的具體類型將由調(diào)用者傳入給map
的transform
方法的返回值類型來決定。
index函數(shù)
找到具體元素的位置,第一次出現(xiàn)的位置
if let index = array.index(where: { element -> Bool in return element == 4 }) {
print("index = \(index)") //index = 2
}
另外一個例子:
let students = ["Kofi", "Abena", "Peter", "Kweku", "Akosua"]
if let i = students.index(where: { $0.hasPrefix("A") }) {
print("\(students[i]) starts with 'A'!")
}
// Prints "Abena starts with 'A'!"
Filter
將數(shù)組中符合一定條件的元素過濾出來并用它們創(chuàng)建一個新的數(shù)組氢拥。對數(shù)組進行循環(huán)并且根據(jù)條件過濾其中元素的模式深滚。
let cast = ["Vivien", "Marlon", "Kim", "Karl"]
let shortNames = cast.filter { $0.characters.count < 5 }
print(shortNames)
// Prints "["Kim", "Karl"]"
filter
內(nèi)部實現(xiàn)
extension Array {
func filter(_ isIncluded: (Element) -> Bool) -> [Element] {
var result: [Element] = []
for x in self where isIncluded(x) {
result.append(x)
}
return result
}
}
組合map
,filter
寫出更簡單的表達根吁,如:查找100以下所有的偶數(shù)
let res = (1..<10).map{$0 * $0}.filter{$0 % 2 == 0}
print(res) //[4, 16, 36, 64]
reduce函數(shù)
對于map
,filter
都是在一個數(shù)組的基礎上產(chǎn)生一個新數(shù)組或者修改數(shù)組轰枝,然后有時候寿烟,你想組合所有的元素到一個新值内边。例如:計算所有元素的和莹汤,我們能夠使用如下代碼:
let number = [0,1,1,2,3,4,5]
var total = 0
for num in number {
total = total + num
}
print("total = \(total)") //total = 16
reduce
方法使用了這種模式,并且抽象為兩個部分,一個初始值红伦,一個是函數(shù)用于組合中間值和元素值陵吸,使用reduce
剩拢,我們的代碼如下:
let sum = number.reduce(0){ total, num in total + num}
//因為操作符+也是函數(shù)饶唤,所以我們可以直接使用+號
let shortSum = fibs.reduce(0, +)
print("sum = \(sum),shortSum = \(shortSum)") //sum = 16,shortSum = 16
注意:輸出的結(jié)果類型并不一定跟元素的類型一樣祸穷,例如:我們想轉(zhuǎn)換integer
到string
let stringArray = number.reduce(""){str, num in str + "\(num)"}
print("stringArray = \(stringArray)") //stringArray = 0112345
reduce
的內(nèi)部實現(xiàn)
extension Array {
func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) -> Result) -> Result {
var result = initialResult
for x in self {
result = nextPartialResult(result, x)
}
return result
}
}
flatMap
通過為序列中的每一個元素進行轉(zhuǎn)換,即執(zhí)行閉包衰倦,返回的數(shù)組是包含串行的結(jié)果.
let numbers = [1, 2, 3, 4]
let mapped = numbers.map { Array(repeating: $0, count: $0) }
print("mapped = \(mapped)") //mapped = [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4]]
let flatMapped = numbers.flatMap { Array(repeating: $0, count: $0) }
print("flatMapped = \(flatMapped)") //flatMapped = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
flatMap
組合元素來自不同的數(shù)組
let suits = ["?", "?", "?", "?"]
let ranks = ["J","Q","K","A"]
let results = suits.flatMap { suit in
ranks.map { rank in
(suit,rank)
}
}
print("results = \(results)")
//results = [("?", "J"), ("?", "Q"), ("?", "K"), ("?", "A"), ("?", "J"), ("?", "Q"), ("?", "K"), ("?", "A"), ("?", "J"), ("?", "Q"), ("?", "K"), ("?", "A"), ("?", "J"), ("?", "Q"), ("?", "K"), ("?", "A")]
flatMap
內(nèi)部實現(xiàn)
extension Array {
func flatMap<T>(_ transform: (Element) -> [T]) -> [T] {
var result: [T] = []
for x in self {
result.append(contentsOf: transform(x))
}
return result
}
}
forEach
forEach
,就像一個循環(huán)我磁,傳遞一個函數(shù)功能用于執(zhí)行序列中的每一個元素孽文。但是不像map
,forEach
并不返回任何值夺艰。
for element in [1,2,3] {
print(element)
}
[1,2,3].forEach { element in
print(element)
}
上下對比一下也沒有看到優(yōu)勢芋哭,但是它能夠隨意使用,如果你想為集合執(zhí)行單一功能的操作郁副,傳遞一個函數(shù)名給forEach
而不是一個比包表達式减牺,這樣能夠看起來更加簡單和精準的代碼。例如:在你的視圖控制器之內(nèi)存谎,你想添加子視圖( subviews)
的數(shù)組到主視圖(main view)
烹植,僅僅只需要 theViews.forEach(view.addSubview)
然而,對于for
循環(huán)和forEach
還是存在一些區(qū)別的愕贡,例如:如果使用for
循環(huán)執(zhí)行返回一條語句草雕,使用forEach
重寫將更好。對于多語句的遍歷固以,不要使用forEach
墩虹。
數(shù)組的切片
除了通過單獨的下標來訪問數(shù)組的元素(fibs[0]
),還可以通過下標來獲取某個范圍中的元素憨琳。比如诫钓,想要獲取除了首個元素的其他元素
let slice = fibs[1..<fibs.endIndex]
slice // 1,1,2,3,5
type(of: slice) // ArraySlice<Int>
它將返回一個數(shù)組切片(slice)
,其中包含了愿數(shù)組中從第二個元素到最后一個元素的數(shù)據(jù)篙螟。得到的結(jié)果類型是ArraySlice
而不是Array
菌湃。
切片類型只是數(shù)組的一種表示方式,它背后的數(shù)據(jù)仍然是原來的數(shù)組遍略,只不過是用切片的方式來進行表示惧所。
字典
字典是一種存儲多個相同類型的值的容器。每個值(value)
都關(guān)聯(lián)唯一的鍵(key)
绪杏,鍵作為字典中的這個值數(shù)據(jù)的標識符下愈。和數(shù)組中的數(shù)據(jù)項不同,字典中的數(shù)據(jù)項并沒有具體順序蕾久。我們在需要通過標識符(鍵)訪問數(shù)據(jù)的時候使用字典势似,這種方法很大程度上和我們在現(xiàn)實世界中使用字典查字義的方法一樣。
字典類型簡化語法
Swift
中的字典使用Dictionary<Key, Value>
定義履因,其中Key
是字典中鍵的數(shù)據(jù)類型,Value
是字典中對應于這些鍵所存儲值的數(shù)據(jù)類型栅迄。我們也可以用[Key: Value]
這樣簡化的形式去創(chuàng)建一個字典類型。雖然這兩種形式功能上相同霞篡,但是后者是首選端逼。
注意:一個字典的Key
類型必須遵循Hashable
協(xié)議,就像Set
的值類型顶滩。
創(chuàng)建一個空字典
我們可以像數(shù)組一樣使用構(gòu)造語法創(chuàng)建一個擁有確定類型的空字典:
var namesOfIntegers = [Int: String]()
// namesOfIntegers 是一個空的 [Int: String] 字典
這個例子創(chuàng)建了一個[Int: String]
類型的空字典來儲存整數(shù)的英語命名。它的鍵是Int
型礁鲁,值是String
型。如果上下文已經(jīng)提供了類型信息仅醇,我們可以使用空字典字面量來創(chuàng)建一個空字典冗美,記作[:]
(中括號中放一個冒號):
namesOfIntegers[16] = "sixteen"
// namesOfIntegers 現(xiàn)在包含一個鍵值對
namesOfIntegers = [:]
// namesOfIntegers 又成為了一個 [Int: String] 類型的空字典
字典的字面量
我們可以使用字典字面量來構(gòu)造字典,和數(shù)組字面量擁有相似語法析二。
字典字面量是一種將一個或多個鍵值對寫作Dictionary
集合的快捷途徑粉洼。一個鍵值對是一個key
和一個value
的結(jié)合體。在字典字面量中叶摄,每一個鍵值對的鍵和值都由冒號分割属韧。這些鍵值對構(gòu)成一個列表,其中這些鍵值對由方括號包含蛤吓、由逗號分割:
[key1: value1, key2: value2, key3: value3]
下面的例子創(chuàng)建了一個存儲國際機場名稱的字典宵喂。在這個字典中鍵是三個字母的國際航空運輸相關(guān)代碼,值是機場名稱:
var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
airports
字典被聲明為一種[String: String]
類型会傲,這意味著這個字典的鍵和值都是String
類型锅棕。
注意:airports
字典被聲明為變量(用var
關(guān)鍵字)而不是常量(let
關(guān)鍵字)因為可能會有更多的機場信息會被添加到這個示例字典中。
airports
字典使用字典字面量初始化淌山,包含兩個鍵值對哲戚。第一對的鍵是YYZ
,值是Toronto Pearson
艾岂。第二對的鍵是DUB
顺少,值是Dublin
。這個字典語句包含了兩個String: String
類型的鍵值對王浴。它們對應airports
變量聲明的類型(一個只有String
鍵和String
值的字典)所以這個字典字面量的任務是構(gòu)造擁有兩個初始數(shù)據(jù)項的airport
字典脆炎。
和數(shù)組一樣,我們在用字典字面量構(gòu)造字典時氓辣,如果它的鍵和值都有各自一致的類型秒裕,那么就不必寫出字典的類型。airports
字典也可以用這種簡短方式定義:
var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
因為這個語句中所有的鍵和值都各自擁有相同的數(shù)據(jù)類型钞啸,Swift
可以推斷出Dictionary<String, String>
是airports
字典的正確類型几蜻。
訪問和修改字典
我們可以通過字典的方法和屬性來訪問和修改字典喇潘,或者通過使用下標語法。和數(shù)組一樣梭稚,我們可以通過字典的只讀屬性count
來獲取某個字典的數(shù)據(jù)項數(shù)量:
print("The dictionary of airports contains \(airports.count) items.")
// 打印 "The dictionary of airports contains 2 items."(這個字典有兩個數(shù)據(jù)項)
使用布爾屬性isEmpty
作為一個縮寫形式去檢查count
屬性是否為0:
if airports.isEmpty {
print("The airports dictionary is empty.")
} else {
print("The airports dictionary is not empty.")
}
// 打印 "The airports dictionary is not empty."
我們也可以在字典中使用下標語法來添加新的數(shù)據(jù)項颖低』】荆可以使用一個恰當類型的鍵作為下標索引,并且分配恰當類型的新值:
airports["LHR"] = "London"
// airports 字典現(xiàn)在有三個數(shù)據(jù)項
我們也可以使用下標語法來改變特定鍵對應的值:
airports["LHR"] = "London Heathrow"
// "LHR"對應的值 被改為 "London Heathrow
作為另一種下標方法莺戒,字典的updateValue(_:forKey:)
方法可以設置或者更新特定鍵對應的值从铲。
就像上面所示的下標示例食店,updateValue(_:forKey:)
方法在這個鍵不存在對應值的時候會設置新值或者在存在時更新已存在的值吉嫩。
和上面的下標方法不同的是updateValue(_:forKey:)
這個方法返回更新值之前的原值嗅定。這樣使得我們可以檢查更新是否成功渠退。
updateValue(_:forKey:)
方法會返回對應值的類型的可選值。舉例來說:對于存儲String值的字典姊扔,這個函數(shù)會返回一個String?或者“可選 String”類型的值恰梢。如果有值存在于更新前嵌言,則這個可選值包含了舊值及穗,否則它將會是nil埂陆。
if let oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") {
print("The old value for DUB was \(oldValue).")
}
// 輸出 "The old value for DUB was Dublin."
我們也可以使用下標語法來在字典中檢索特定鍵對應的值。因為有可能請求的鍵沒有對應的值存在购裙,字典的下標訪問會返回對應值的類型的可選值缓窜。如果這個字典包含請求鍵所對應的值禾锤,下標會返回一個包含這個存在值的可選值恩掷,否則將返回nil:
if let airportName = airports["DUB"] {
print("The name of the airport is \(airportName).")
} else {
print("That airport is not in the airports dictionary.")
}
// 打印 "The name of the airport is Dublin Airport."
我們還可以使用下標語法來通過給某個鍵的對應值賦值為nil來從字典里移除一個鍵值對:
airports["APL"] = "Apple Internation"
// "Apple Internation" 不是真的 APL 機場, 刪除它
airports["APL"] = nil
// APL 現(xiàn)在被移除了
此外黄娘,removeValue(forKey:)
方法也可以用來在字典中移除鍵值對克滴。這個方法在鍵值對存在的情況下會移除該鍵值對并且返回被移除的值或者在沒有值的情況下返回nil:
if let removedValue = airports.removeValue(forKey: "DUB") {
print("The removed airport's name is \(removedValue).")
} else {
print("The airports dictionary does not contain a value for DUB.")
}
// prints "The removed airport's name is Dublin Airport."
字典遍歷
我們可以使用for-in
循環(huán)來遍歷某個字典中的鍵值對劝赔。每一個字典中的數(shù)據(jù)項都以(key, value)
元組形式返回着帽,并且我們可以使用臨時常量或者變量來分解這些元組:
for (airportCode, airportName) in airports {
print("\(airportCode): \(airportName)")
}
// YYZ: Toronto Pearson
// LHR: London Heathrow
通過訪問keys或者values屬性仍翰,我們也可以遍歷字典的鍵或者值:
for airportCode in airports.keys {
print("Airport code: \(airportCode)")
}
// Airport code: YYZ
// Airport code: LHR
for airportName in airports.values {
print("Airport name: \(airportName)")
}
// Airport name: Toronto Pearson
// Airport name: London Heathrow
如果我們只是需要使用某個字典的鍵集合或者值集合來作為某個接受Array實例的 API 的參數(shù)予借,可以直接使用keys或者values屬性構(gòu)造一個新數(shù)組:
let airportCodes = [String](airports.keys)
// airportCodes 是 ["YYZ", "LHR"]
let airportNames = [String](airports.values)
// airportNames 是 ["Toronto Pearson", "London Heathrow"]
Swift的字典類型是無序集合類型。為了以特定的順序遍歷字典的鍵或值喧笔,可以對字典的keys或values屬性使用sorted()方法书闸。
合并兩個字典
合并兩個字典浆劲,用來做合并的字典需要覆蓋重復的鍵
擴展Dictionary
類型,為它添加一個merge
方法度气,該方法接受帶合并的字典作為參數(shù)膨报。我們可以將參數(shù)指明為Dictionary
類型现柠,不過更好的選擇是用更加通用的泛型方法來進行實現(xiàn)
我們對參數(shù)的要求是够吩,它必須是一個序列,這樣我們就可以對其進行枚舉遍歷强法,另外饮怯,序列的元素必須是鍵值對硕淑,而且它必須接受調(diào)用的字典的鍵值值擁有相同的類型嘉赎。對于任意的Sequence
公条,如果它的Iterator.Element
是(Key,Value)
話靶橱,它就滿足我們的要求
extension Dictionary {
mutating func merge<S>(_ other: S) where S: Sequence,S.Iterator.Element == (key: Key,value: Value) {
for (k,v) in other {
self[k] = v
}
}
}
再擴展一個字典的初始化方法关霸,通過字典創(chuàng)建字典
extension Dictionary {
init<S: Sequence>(_ sequence: S) where S.Iterator.Element == (key: Key,value: Value) {
self = [:]
self.merge(sequence)
}
}
對字典的values
進行映射
extension Dictionary {
func mapValues<NewValue>(transform: (Value) -> NewValue) ->[Key: NewValue] {
return Dictionary<Key,NewValue>(map { (key, value) in
return (key, transform(value))
})
}
}
Hashable要求
字典其實是哈希表队寇,字典通過鍵的hashValue
來為每一個鍵指定一個位置,以及它所對應的存儲识埋。這也就是Dictionary
要求它的key
類型需要遵守Hashable
協(xié)議的原因窒舟。標準庫中所有的基本數(shù)據(jù)類型都是遵守Hashable
協(xié)議的惠豺,它們包括字符串,整數(shù)蛹疯,浮點數(shù)以及布爾值苍苞。不帶關(guān)聯(lián)值的枚舉值類型也會自動遵守Hashable
如果想將自定義的類型用作字典的key
狼纬,那么必須手動為自定義類型添加Hashable
并滿足它疗琉,這需要你實現(xiàn)hashValue
屬性盈简。另外柠贤,因為Hashable
本身是對Equatable
的擴展类缤,因此餐弱,還需要為自定義類型重載==
運算符膏蚓。
實現(xiàn)必須保證哈希不變原則:兩個同樣的實例,必須擁有同樣的哈希值氓扛,反過來不必為真:兩個哈希值相同的實例不一定需要相等幢尚。不同的哈希值的數(shù)量是有限的,然后很多可以被哈希的類型(比如:字符串)的個數(shù)是無窮的真慢。
哈虾诮纾可能重復這一特性皂林,意味著Dictionary
必須能夠處理哈希碰撞础倍。優(yōu)秀的哈希算法總是能給出較少的碰撞沟启,這將保持集合的性能特性。理想狀態(tài)下芽卿,我們希望得到的哈希值在整個整數(shù)范圍內(nèi)均勻分布卸例。在極端的例子下筷转,如果你的實現(xiàn)對所有實例返回相同的哈希值呜舒,那么這個字典的查找性能下降到O(n)
優(yōu)秀哈希算法的第二個特質(zhì)是它應該很快摊滔,記住艰躺,在字典中進行插入腺兴,刪除,或者查找時篓足,這些哈希值都要被計算栈拖。如果你的hashValue實現(xiàn)要消耗太多時間涩哟,那么它很可能會拖慢你的程序,讓你的字典從O(1)
特性中得到的好處損失殆盡
下一個能同時做到這些要求的哈希算法并不容易潜腻,對于一些由本身就是Hashable
的數(shù)據(jù)類型組成的類型來說融涣,將成員的哈希值進行“異或”運算往往是一個不錯的起點
struct Person {
var name: String
var zipCode: Int
var birthday: Date
}
extension Person: Equatable {
static func ==(lhs: Person, rhs: Person) -> Bool {
return lhs.name == rhs.name && lhs.zipCode == rhs.zipCode && lhs.birthday == rhs.birthday
}
}
extension Person: Hashable {
var hashValue: Int {
return name.hashValue ^ zipCode.hashValue ^ birthday.hashValue
}
}
異或計算方法的一個限制是威鹿,這個操作本身是左右對稱的(也就是說a^b == b^a
)杂拨,對于某些數(shù)據(jù)的哈希計算悯衬,這有時候會造成不必要的碰撞筋粗,你可以添加一個位旋轉(zhuǎn)并混合使用它們來避免這個問題
最后娜亿,當你使用不具有值語義的類型(比如可變的對象)作為字典的鍵時,需要特別小心沛婴。如果你在將一個對象用做字典的鍵后嘁灯,改變了它的內(nèi)容躲舌,它的哈希值/或相等特性往往也會發(fā)生改變,這時候你將無法再在字典中找到它秒旋,這時字典在錯誤的位置存儲對象迁筛,這將導致字典內(nèi)部存儲錯誤耕挨,對于值類型來說,因為字典中的鍵不會和復制的值共用存儲酒甸,因此它也不會被從外部改變插勤,所以不存在這個問題
Set
集合(Set)用來存儲相同類型并且沒有確定順序的值农尖。當集合元素順序不重要時或者希望確保每個元素只出現(xiàn)一次時可以使用集合而不是數(shù)組良哲。
注意
Swift 的Set
類型被橋接到Foundation
中的NSSet
類筑凫。
關(guān)于使用Foundation
和Cocoa
中Set
的知識,參見 Using Swift with Cocoa and Obejective-C(Swift 4.1) 中使用 Cocoa 數(shù)據(jù)類型部分滓技。
集合類型的哈希值
一個類型為了存儲在集合中令漂,該類型必須是可哈系兀化的--也就是說妹窖,該類型必須提供一個方法來計算它的哈希值嘱吗。一個哈希值是Int
類型的,相等的對象哈希值必須相同哆致,比如a==b
,因此必須a.hashValue == b.hashValue
患膛。
Swift
的所有基本類型(比如String,Int,Double和Bool
)默認都是可哈献俚牛化的跃捣,可以作為集合的值的類型或者字典的鍵的類型。沒有關(guān)聯(lián)值的枚舉成員值(在枚舉有講述)默認也是可哈虾ㄕ停化的闻镶。
注意:
你可以使用你自定義的類型作為集合的值的類型或者是字典的鍵的類型丸升,但你需要使你的自定義類型符合 Swift
標準庫中的Hashable
協(xié)議。符合Hashable
協(xié)議的類型需要提供一個類型為Int
的可讀屬性hashValue
墩剖。由類型的hashValue
屬性返回的值不需要在同一程序的不同執(zhí)行周期或者不同程序之間保持相同涛碑。
public struct Set<Element> where Element : Hashable {}
public protocol Hashable : Equatable {
public var hashValue: Int { get }
public func hash(into hasher: inout Hasher)
}
public protocol Equatable {
public static func == (lhs: Self, rhs: Self) -> Bool
}
因為Hashable
協(xié)議符合Equatable
協(xié)議孵淘,所以遵循該協(xié)議的類型也必須提供一個"是否相等"運算符(==)
的實現(xiàn)瘫证。這個Equatable
協(xié)議要求任何符合==
實現(xiàn)的實例間都是一種相等的關(guān)系背捌。也就是說毡庆,對于a,b,c
三個值來說么抗,==
的實現(xiàn)必須滿足下面三種情況:
a == a(自反性)
a == b意味著b == a(對稱性)
a == b && b == c意味著a == c(傳遞性)
集合類型語法
Swift
中的Set
類型被寫為Set<Element>
,這里的Element
表示Set
中允許存儲的類型螟加,和數(shù)組不同的是捆探,集合沒有等價的簡化形式黍图。
創(chuàng)建一個空的集合
你可以通過構(gòu)造器語法創(chuàng)建一個特定類型的空集合:
var letters = Set<Character>()
print("letters is of type Set<Character> with \(letters.count) items.")
// 打印 "letters is of type Set<Character> with 0 items."
注意:通過構(gòu)造器雌隅,這里的letters
變量的類型被推斷為Set<Character>
此外恰起,如果上下文提供了類型信息检盼,比如作為函數(shù)的參數(shù)或者已知類型的變量或常量吨枉,我們可以通過一個空的數(shù)組字面量創(chuàng)建一個空的Set
:
letters.insert("a")
// letters 現(xiàn)在含有1個 Character 類型的值
letters = []
// letters 現(xiàn)在是一個空的 Set, 但是它依然是 Set<Character> 類型
用數(shù)組字面量創(chuàng)建集合
你可以使用數(shù)組字面量來構(gòu)造集合貌亭,并且可以使用簡化形式寫一個或者多個值作為集合元素圃庭。
下面的例子創(chuàng)建一個稱之為favoriteGenres
的集合來存儲String
類型的值:
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
// favoriteGenres 被構(gòu)造成含有三個初始值的集合
這個favoriteGenres
變量被聲明為一個String
值的集合”失晴,寫為Set<String>
涂屁。由于這個特定的集合含有指定String
類型的值拆又,所以它只允許存儲String
類型值。這里的favoriteGenres
變量有三個String
類型的初始值("Rock"发笔,"Classical"和"Hip hop")
了讨,并以數(shù)組字面量的方式出現(xiàn)前计。
注意:favoriteGenres
被聲明為一個變量(擁有var
標示符)而不是一個常量(擁有let
標示符),因為它里面的元素將會在下面的例子中被增加或者移除男杈。
一個Set
類型不能從數(shù)組字面量中被單獨推斷出來伶棒,因此Set類型必須顯式聲明肤无。然而宛渐,由于 Swift
的類型推斷功能窥翩,如果你想使用一個數(shù)組字面量構(gòu)造一個Set并且該數(shù)組字面量中的所有元素類型相同,那么你無須寫出Set
的具體類型鳞仙。favoriteGenres
的構(gòu)造形式可以采用簡化的方式代替:
var favoriteGenres1: Set = ["Rock", "Classical", "Hip hop"]
//由于數(shù)組字面量中的所有元素類型相同寇蚊,Swift 可以推斷出Set<String>作為favoriteGenres變量的正確類型。
訪問和修改一個集合
你可以通過Set
的屬性和方法來訪問和修改Set
棍好。為了找出Set
中元素的數(shù)量仗岸,可以使用其只讀屬性count:
print("I have \(favoriteGenres.count) favorite music genres.")
// 打印 "I have 3 favorite music genres."
使用布爾屬性isEmpty
作為一個縮寫形式去檢查count
屬性是否為0:
if favoriteGenres.isEmpty {
print("As far as music goes, I'm not picky.")
} else {
print("I have particular music preferences.")
}
// 打印 "I have particular music preferences."
你可以通過調(diào)用Set的insert(_:)方法來添加一個新元素:
favoriteGenres.insert("Jazz")
// favoriteGenres 現(xiàn)在包含4個元素
你可以通過調(diào)用Set
的remove(_:)
方法去刪除一個元素,如果該值是該Set
的一個元素則刪除該元素并且返回被刪除的元素值梳玫,否則如果該Set
不包含該值爹梁,則返回nil
。另外提澎,Set
中的所有元素可以通過它的removeAll()
方法刪除。
if let removedGenre = favoriteGenres.remove("Rock") {
print("\(removedGenre)? I'm over it.")
} else {
print("I never much cared for that.")
}
// 打印 "Rock? I'm over it."
使用contains(_:)
方法去檢查Set
中是否包含一個特定的值:
if favoriteGenres.contains("Funk") {
print("I get up on the good foot.")
} else {
print("It's too funky in here.")
}
// 打印 "It's too funky in here."
遍歷一個集合
你可以在一個for-in
循環(huán)中遍歷一個Set
中的所有值盼忌。
for genre in favoriteGenres {
print("\(genre)")
}
// Classical
// Jazz
// Hip hop
Swift
的Set
類型沒有確定的順序,為了按照特定順序來遍歷一個Set
中的值可以使用sorted()
方法川慌,它將返回一個有序數(shù)組,這個數(shù)組的元素排列順序由操作符<
對元素進行比較的結(jié)果來確定.
for genre in favoriteGenres.sorted() {
print("\(genre)")
}
// prints "Classical"
// prints "Hip hop"
// prints "Jazz
集合操作
你可以高效地完成Set
的一些基本操作,比如把兩個集合組合到一起,判斷兩個集合共有元素,或者判斷兩個集合是否全包含于置,部分包含或者不相交瞄桨。
基本集合操作
下面的插圖描述了兩個集合-a和b-以及通過陰影部分的區(qū)域顯示集合各種操作的結(jié)果。
1 使用intersection(_:)
方法根據(jù)兩個集合中都包含的值創(chuàng)建的一個新的集合。
2 使用symmetricDifference(_:)
方法根據(jù)在一個集合中但不在兩個集合中的值創(chuàng)建一個新的集合。
3 使用union(_:)
方法根據(jù)兩個集合的值創(chuàng)建一個新的集合宽菜。
4 使用subtracting(_:)
方法根據(jù)不在該集合中的值創(chuàng)建一個新的集合烈菌。
let oddDigits: Set = [1, 3, 5, 7, 9]
let evenDigits: Set = [0, 2, 4, 6, 8]
let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]
oddDigits.union(evenDigits).sorted()
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
oddDigits.intersection(evenDigits).sorted()
// []
oddDigits.subtracting(singleDigitPrimeNumbers).sorted()
// [1, 9]
oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted()
// [1, 2, 9]
集合成員關(guān)系和相等
下面的插圖描述了三個集合-a,b和c,以及通過重疊區(qū)域表述集合間共享的元素。集合a是集合b的父集合,因為a包含了b中所有的元素,相反的举反,集合b是集合a的子集合雕崩,因為屬于b的元素也被a包含粗蔚。集合b和集合c彼此不關(guān)聯(lián)当辐,因為它們之間沒有共同的元素寺晌。
1 使用“是否相等”運算符(==
)來判斷兩個集合是否包含全部相同的值陆赋。
2 使用isSubset(of:)
方法來判斷一個集合中的值是否也被包含在另外一個集合中灾锯。
3 使用isSuperset(of:)
方法來判斷一個集合中包含另一個集合中所有的值凌那。
4 使用isStrictSubset(of:)或者isStrictSuperset(of:)
方法來判斷一個集合是否是另外一個集合的子集合或者。父集合并且兩個集合并不相等。
5 使用isDisjoint(with:)
方法來判斷兩個集合是否不含有相同的值(是否沒有交集)。
let a: Set = ["1","2","3","4","5","6"]
let b: Set = ["1","2"]
let c: Set = ["7"]
b.isSubset(of: a) // true
a.isSuperset(of: b) // true
a.isDisjoint(with: c) // true
Set擴展
集合唯一和有序性
為Sequence
添加一個擴展,用于獲取Sequence
中所有唯一的元素髓绽。因為我們是很容易將所有的元素放到Set中,并且返回內(nèi)容,但是這并不穩(wěn)定,因為Set
的順序是未定義的藤滥,為了保證輸入元素的順序和唯一性标沪。進行如下擴展
extension Sequence where Iterator.Element: Hashable {
func unique() -> [Iterator.Element] {
var seen: Set<Iterator.Element> = []
return filter({
if seen.contains($0) {
return false
}else {
seen.insert($0)
return true
}
})
}
}
[1,2,3,12,1,3,4,5,6,4,6].unique() // [1, 2, 3, 12, 4, 5, 6]
數(shù)組和集合的對比
- Set用于無序的唯一對象币他,Array是有序的并且可以包含重復數(shù)據(jù)
- Array迭代速度比Set看尿这,Set搜索速度比Array快
參考
《Swift進階》
Collection Types