Swift5.4 语言指南(七) 集合类型
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/)
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/9723114.html
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
Swift提供了三种主要的集合类型,称为数组,集合和字典,用于存储值的集合。数组是值的有序集合。集是唯一值的无序集合。字典是键-值关联的无序集合。
Swift中的数组,集合和字典始终清楚它们可以存储的值和键的类型。这意味着您不能将错误类型的值错误地插入到集合中。这也意味着您可以对将从集合中检索到的值的类型充满信心。
笔记
Swift的数组,集合和字典类型被实现为泛型集合。有关泛型类型和集合的更多信息,请参见泛型。
集合的可变性
如果创建数组,集合或字典,并将其分配给变量,则创建的集合将是mutable。这意味着您可以在创建集合后通过添加,删除或更改集合中的项目来更改(或变异)集合。如果将数组,集合或字典分配给常量,则该集合是不可变的,并且其大小和内容无法更改。
笔记
在不需要更改集合的所有情况下,创建不可变的集合是一个好习惯。这样做可以使您更轻松地推理代码,并使Swift编译器可以优化创建的集合的性能。
数组
一个阵列存储值在有序列表中的相同类型的。同一值可以在数组中的不同位置多次出现。
数组类型简写语法
Swift数组的类型完整写为Array<Element>
,其中Element
是允许存储数组的值的类型。您也可以将简写形式的数组类型编写为[Element]
。尽管这两种形式在功能上是相同的,但速写形式是首选,在引用数组的类型时,本指南通篇使用速记形式。
创建一个空数组
您可以使用初始化语法创建某种类型的空数组:
- var someInts = [Int]()
- print("someInts is of type [Int] with \(someInts.count) items.")
- // Prints "someInts is of type [Int] with 0 items."
请注意,someInts
变量[Int]
的类型是从初始化程序的类型推断出来的。
或者,如果上下文已经提供了类型信息,例如函数参数或已经键入的变量或常量,则可以创建一个空数组,其中包含一个空数组文字,写为[]
(一个空的方括号对):
- someInts.append(3)
- // someInts now contains 1 value of type Int
- someInts = []
- // someInts is now an empty array, but is still of type [Int]
创建具有默认值的数组
Swift的Array
类型还提供了一个初始化程序,用于创建一个特定大小的数组,并将其所有值都设置为相同的默认值。您向初始化程序传递了适当类型的默认值(称为repeating
):以及该值在新数组中重复的次数(称为count
):
- var threeDoubles = Array(repeating: 0.0, count: 3)
- // threeDoubles is of type [Double], and equals [0.0, 0.0, 0.0]
通过将两个数组加在一起来创建数组
您可以使用加法运算符(+
)将两个具有兼容类型的现有数组加在一起,从而创建一个新数组。从添加到一起的两个数组的类型可以推断出新数组的类型:
- var anotherThreeDoubles = Array(repeating: 2.5, count: 3)
- // anotherThreeDoubles is of type [Double], and equals [2.5, 2.5, 2.5]
- var sixDoubles = threeDoubles + anotherThreeDoubles
- // sixDoubles is inferred as [Double], and equals [0.0, 0.0, 0.0, 2.5, 2.5, 2.5]
使用数组文字创建数组
您还可以使用数组常量初始化数组,这是将一个或多个值写为数组集合的一种简便方法。数组文字被写为一个值列表,以逗号分隔,并用一对方括号括起来:
- [value 1, value 2, value 3]
下面的示例创建一个名为shoppingList
存储String
值的数组:
- var shoppingList: [String] = ["Eggs", "Milk"]
- // shoppingList has been initialized with two initial items
将该shoppingList
变量声明为“字符串值数组”,写为[String]
。由于此特定数组将值类型指定为String
,因此String
仅允许存储值。在此,shoppingList
使用两个String
值("Eggs"
和"Milk"
)初始化数组,并将其写入数组文字中。
笔记
由于在下面的示例中将更多商品添加到购物清单,因此该shoppingList
数组被声明为变量(带有var
介绍者),而不是常量(带有let
介绍者)。
在这种情况下,数组文字包含两个String
值,而没有其他值。这与shoppingList
变量声明的类型(只能包含String
值的数组)匹配,因此,允许使用数组文字的赋值作为shoppingList
使用两个初始项进行初始化的方式。
借助Swift的类型推断,如果使用包含相同类型值的数组文字进行初始化,则不必编写数组的类型。的初始化shoppingList
可能以较短的形式写成:
- var shoppingList = ["Eggs", "Milk"]
因为数组文字中的所有值都是相同的类型,所以Swift可以推断出[String]
该shoppingList
变量是正确的类型。
访问和修改数组
您可以通过数组的方法和属性或使用下标语法来访问和修改数组。
要找出数组中的项目数,请检查其只读count
属性:
- print("The shopping list contains \(shoppingList.count) items.")
- // Prints "The shopping list contains 2 items."
使用BooleanisEmpty
属性作为检查该count
属性是否等于的快捷方式0
:
- if shoppingList.isEmpty {
- print("The shopping list is empty.")
- } else {
- print("The shopping list isn't empty.")
- }
- // Prints "The shopping list isn't empty."
您可以通过调用数组的append(_:)
方法将新项目添加到数组的末尾:
- shoppingList.append("Flour")
- // shoppingList now contains 3 items, and someone is making pancakes
或者,在附加赋值运算符(+=
)后面附加一个或多个兼容项的数组:
- shoppingList += ["Baking Powder"]
- // shoppingList now contains 4 items
- shoppingList += ["Chocolate Spread", "Cheese", "Butter"]
- // shoppingList now contains 7 items
通过使用下标语法从数组中检索一个值,在数组名称后紧接着在方括号内传递要检索的值的索引:
- var firstItem = shoppingList[0]
- // firstItem is equal to "Eggs"
笔记
数组中的第一项索引为0
,而不是1
。Swift中的数组始终为零索引。
您可以使用下标语法来更改给定索引处的现有值:
- shoppingList[0] = "Six eggs"
- // the first item in the list is now equal to "Six eggs" rather than "Eggs"
使用下标语法时,您指定的索引必须有效。例如,编写尝试将项目追加到数组末尾的操作会导致运行时错误。shoppingList[shoppingList.count] = "Salt"
您也可以使用下标语法立即更改值的范围,即使替换值集的长度与要替换的范围的长度不同。下面的示例替换,以及与和:"Chocolate Spread"
"Cheese"
"Butter"
"Bananas"
"Apples"
- shoppingList[4...6] = ["Bananas", "Apples"]
- // shoppingList now contains 6 items
要将项目以指定的索引插入数组,请调用数组的insert(_:at:)
方法:
- shoppingList.insert("Maple Syrup", at: 0)
- // shoppingList now contains 7 items
- // "Maple Syrup" is now the first item in the list
对该insert(_:at:)
方法的调用会在购物清单的最开头插入一个值为的新商品,该商品的索引为。"Maple Syrup"
0
同样,您可以使用remove(at:)
方法从数组中删除一个项目。此方法将删除指定索引处的项目并返回已删除的项目(尽管如果不需要,您可以忽略返回的值):
- let mapleSyrup = shoppingList.remove(at: 0)
- // the item that was at index 0 has just been removed
- // shoppingList now contains 6 items, and no Maple Syrup
- // the mapleSyrup constant is now equal to the removed "Maple Syrup" string
笔记
如果尝试访问或修改数组现有范围之外的索引值,则会触发运行时错误。您可以通过将索引与数组的count
属性进行比较来检查索引是否有效。在阵列中的最大有效的索引是因为数组是从零然而索引,当被(意味着数组为空),没有有效的索引。count - 1
count
0
删除项目后,数组中的所有间隙都将关闭,因此index的值0
再次等于:"Six eggs"
- firstItem = shoppingList[0]
- // firstItem is now equal to "Six eggs"
如果要从数组中删除最后一项,请使用removeLast()
方法而不是remove(at:)
方法来避免查询数组的count
属性。与remove(at:)
方法一样,removeLast()
返回删除的项目:
- let apples = shoppingList.removeLast()
- // the last item in the array has just been removed
- // shoppingList now contains 5 items, and no apples
- // the apples constant is now equal to the removed "Apples" string
遍历数组
可以遍历整个集合值与数组for
-in
循环:
- for item in shoppingList {
- print(item)
- }
- // Six eggs
- // Milk
- // Flour
- // Baking Powder
- // Bananas
如果需要每个项目的整数索引及其值,请使用enumerated()
方法来遍历数组。对于数组中的每个项目,该enumerated()
方法都返回一个由整数和该项目组成的元组。整数从零开始,每一项加一。如果您对整个数组进行枚举,则这些整数与项的索引匹配。您可以在迭代过程中将元组分解为临时常量或变量:
- for (index, value) in shoppingList.enumerated() {
- print("Item \(index + 1): \(value)")
- }
- // Item 1: Six eggs
- // Item 2: Milk
- // Item 3: Flour
- // Item 4: Baking Powder
- // Item 5: Bananas
欲了解更多有关for
-in
循环,见为,在循环中。
套
甲组存储不同集合中没有定义排序的相同类型的值。当项目的顺序不重要时,或者需要确保某个项目仅出现一次时,可以使用集合而不是数组。
集合类型的哈希值
类型必须是可哈希的才能存储在集合中,也就是说,该类型必须提供一种为其自身计算哈希值的方法。哈希值是Int
对于相等比较的所有对象都相同的值,例如,如果,则的哈希值等于的哈希值。a == b
a
b
所有斯威夫特的基本类型(例如String
,Int
,Double
,和Bool
)默认情况下可哈希,并可以作为设定值类型或字典密钥类型。默认情况下,没有关联值的枚举案例值(如Enumerations中所述)也是可哈希的。
设置类型语法
Swift集的类型写为Set<Element>
,其中Element
是该集允许存储的类型。与数组不同,集合没有等效的速记形式。
创建和初始化一个空集
您可以使用初始化语法创建一个特定类型的空集:
- var letters = Set<Character>()
- print("letters is of type Set<Character> with \(letters.count) items.")
- // Prints "letters is of type Set<Character> with 0 items."
笔记
的类型的letters
变量被推断为Set<Character>
,从初始的类型。
或者,如果上下文已经提供了类型信息,例如函数参数或已经键入的变量或常量,则可以使用空数组文字创建一个空集:
- letters.insert("a")
- // letters now contains 1 value of type Character
- letters = []
- // letters is now an empty set, but is still of type Set<Character>
使用数组文字创建集合
您还可以使用数组文字初始化集合,这是将一个或多个值写为集合的一种简便方法。
下面的示例创建一个名为favoriteGenres
存储String
值的集合:
- var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
- // favoriteGenres has been initialized with three initial items
将该favoriteGenres
变量声明为“一组String
值”,写为Set<String>
。由于此特定集合将值类型指定为String
,因此仅允许存储String
值。在此,favoriteGenres
集合被初始化具有三个String
值("Rock"
,"Classical"
,和),阵列字面内写入。"Hip hop"
笔记
因为在以下示例中添加和删除了项,所以该favoriteGenres
集合被声明为变量(带有var
引入程序)而不是常量(带有let
引入程序)。
不能仅从数组文字中推断出集合类型,因此Set
必须显式声明该类型。但是,由于Swift的类型推断,如果使用仅包含一种类型值的数组文字进行初始化,则不必编写集合元素的类型。的初始化favoriteGenres
可能以较短的形式写成:
- var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]
因为数组文字中的所有值都是相同的类型,所以Swift可以推断出Set<String>
该favoriteGenres
变量是正确的类型。
访问和修改集合
您可以通过集合的方法和属性来访问和修改集合。
要找出集合中的项目数,请检查其只读count
属性:
- print("I have \(favoriteGenres.count) favorite music genres.")
- // Prints "I have 3 favorite music genres."
使用BooleanisEmpty
属性作为检查该count
属性是否等于的快捷方式0
:
- if favoriteGenres.isEmpty {
- print("As far as music goes, I'm not picky.")
- } else {
- print("I have particular music preferences.")
- }
- // Prints "I have particular music preferences."
您可以通过调用集合的insert(_:)
方法将新项目添加到集合中:
- favoriteGenres.insert("Jazz")
- // favoriteGenres now contains 4 items
您可以通过调用集合的remove(_:)
方法从集合中删除项目,该方法将删除该项目(如果它是集合的成员),并返回删除的值,或者nil
如果集合中不包含该值,则返回该值。或者,可以使用其removeAll()
方法删除集合中的所有项目。
- if let removedGenre = favoriteGenres.remove("Rock") {
- print("\(removedGenre)? I'm over it.")
- } else {
- print("I never much cared for that.")
- }
- // Prints "Rock? I'm over it."
若要检查集合是否包含特定项目,请使用contains(_:)
方法。
- if favoriteGenres.contains("Funk") {
- print("I get up on the good foot.")
- } else {
- print("It's too funky in here.")
- }
- // Prints "It's too funky in here."
遍历一组
您可以使用for
-in
循环遍历集合中的值。
- for genre in favoriteGenres {
- print("\(genre)")
- }
- // Classical
- // Jazz
- // Hip hop
欲了解更多有关for
-in
循环,见为,在循环中。
Swift的Set
类型没有定义的顺序。若要按特定顺序遍历集合的值,请使用sorted()
方法,该方法将集合的元素作为使用<
操作符排序的数组返回。
- for genre in favoriteGenres.sorted() {
- print("\(genre)")
- }
- // Classical
- // Hip hop
- // Jazz
执行集合操作
您可以有效地执行基本的集合操作,例如将两个集合组合在一起,确定两个集合具有哪些共同的值,或者确定两个集合是否包含全部,部分或不包含相同的值。
基本设置操作
下图描绘了两个集合-a
和b
-,其中各个集合操作的结果由阴影区域表示。
- 使用该
intersection(_:)
方法创建仅具有两个集合共有的值的新集合。 - 使用该
symmetricDifference(_:)
方法创建一个新集合,其中任一集合中都有值,但不能同时包含两者。 - 使用该
union(_:)
方法创建一个包含两个集合中所有值的新集合。 - 使用该
subtracting(_:)
方法创建一个新集合,其值不在指定集合中。
- 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]
设置成员资格和平等
下图描述了和的三个集合a
,b
其中c
重叠的区域表示集合之间共享的元素。Seta
是set的超集b
,因为它a
包含中的所有元素b
。相反,setb
是set的子集a
,因为inb
中的所有元素也包含在中a
。集b
和集c
彼此不相交,因为它们没有共同的元素。
- 使用“等于”运算符(
==
)确定两组是否包含所有相同的值。 - 使用该
isSubset(of:)
方法确定集合中的所有值是否都包含在指定集合中。 - 使用该
isSuperset(of:)
方法确定集合是否包含指定集合中的所有值。 - 使用
isStrictSubset(of:)
或isStrictSuperset(of:)
方法来确定集合是子集还是超集,但不等于指定的集。 - 使用该
isDisjoint(with:)
方法确定两个集合是否没有共同的值。
- let houseAnimals: Set = ["🐶", "🐱"]
- let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"]
- let cityAnimals: Set = ["🐦", "🐭"]
- houseAnimals.isSubset(of: farmAnimals)
- // true
- farmAnimals.isSuperset(of: houseAnimals)
- // true
- farmAnimals.isDisjoint(with: cityAnimals)
- // true
辞典
甲字典存储相同类型的密钥和一个集合中的相同类型的值与没有定义排序之间的关联。每个值都与唯一键相关联,该键充当字典中该值的标识符。与数组中的项目不同,字典中的项目没有指定的顺序。当您需要根据其标识符查找值时,可以使用字典,这与使用现实世界字典查找特定单词的定义的方式几乎相同。
笔记
Swift的Dictionary
类型被桥接到Foundation的NSDictionary
类。
有关Dictionary
与Foundation和Cocoa一起使用的更多信息,请参见在字典和NSDictionary之间架桥。
字典类型速记语法
Swift字典的类型完整写为,其中是可以用作字典键的值的类型,并且是字典为这些键存储的值的类型。Dictionary<Key, Value>
Key
Value
笔记
字典Key
类型必须符合Hashable
协议,例如集合的值类型。
您还可以将简写形式的字典类型写为。尽管两种形式在功能上是相同的,但速记形式是首选,在引用字典类型时在本指南中使用。[Key: Value]
创建一个空字典
与数组一样,可以Dictionary
使用初始化程序语法创建某种类型的空值:
- var namesOfIntegers = [Int: String]()
- // namesOfIntegers is an empty [Int: String] dictionary
本示例创建一个空的类型字典来存储人类可读的整数值名称。它的键是类型,其值是类型。[Int: String]
Int
String
如果上下文已经提供了类型信息,则可以使用一个空的字典文字创建一个空的字典,该文字被写为[:]
(一对方括号内的冒号):
- namesOfIntegers[16] = "sixteen"
- // namesOfIntegers now contains 1 key-value pair
- namesOfIntegers = [:]
- // namesOfIntegers is once again an empty dictionary of type [Int: String]
用字典文字创建字典
您还可以使用字典常量初始化字典,字典常量的语法与前面看到的数组常量相似。字典文字是将一个或多个键/值对作为Dictionary
集合编写的一种简便方法。
甲键值对是一个键和值的组合。在字典文字中,每个键值对中的键和值都用冒号分隔。键值对以列表形式编写,并用逗号分隔,并用方括号包围:
- [key 1: value 1, key 2: value 2, key 3: value 3]
下面的示例创建一个字典来存储国际机场的名称。在此字典中,键是三个字母的国际航空运输协会代码,值是机场名称:
- var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
该airports
字典被声明为具有式的,意思是“一键均为类型的,并且其值是类型的也”。[String: String]
Dictionary
String
String
笔记
由于在下面的示例中将更多的机场添加到字典中,因此该airports
字典被声明为变量(带有var
引入程序),而不是常量(带有let
引入程序)。
该airports
字典被初始化为一个字典字面含有两个键-值对。第一对具有的键"YYZ"
和的值。第二对具有的键和的值。"Toronto Pearson"
"DUB"
"Dublin"
该词典文字包含两对。此键值类型与变量声明的类型(仅包含键和值的字典)匹配,因此,允许使用字典常量来分配带有两个初始项的字典来初始化字典。String: String
airports
String
String
airports
与数组一样,如果使用其键和值具有一致类型的字典文字来初始化字典,则不必编写字典的类型。的初始化airports
可能以较短的形式写成:
- var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
因为文字中的所有键彼此都具有相同的类型,并且所有值也都具有相同的类型,所以Swift可以推断出这是用于字典的正确类型。[String: String]
airports
访问和修改字典
您可以通过其方法和属性或使用下标语法来访问和修改字典。
与数组一样,您可以Dictionary
通过检查其只读count
属性来找出a中的项目数:
- print("The airports dictionary contains \(airports.count) items.")
- // Prints "The airports dictionary contains 2 items."
使用BooleanisEmpty
属性作为检查该count
属性是否等于的快捷方式0
:
- if airports.isEmpty {
- print("The airports dictionary is empty.")
- } else {
- print("The airports dictionary isn't empty.")
- }
- // Prints "The airports dictionary isn't empty."
您可以使用下标语法将新项目添加到字典中。使用适当类型的新键作为下标索引,并分配适当类型的新值:
- airports["LHR"] = "London"
- // the airports dictionary now contains 3 items
您还可以使用下标语法来更改与特定键关联的值:
- airports["LHR"] = "London Heathrow"
- // the value for "LHR" has been changed to "London Heathrow"
作为下标的替代updateValue(_:forKey:)
方法,请使用字典的方法来设置或更新特定键的值。像上面的下标示例一样,updateValue(_:forKey:)
如果不存在键,则该方法设置键的值;如果键已经存在,则更新该值。但是,与下标不同,该updateValue(_:forKey:)
方法在执行更新后会返回旧值。这使您可以检查是否进行了更新。
该updateValue(_:forKey:)
方法返回字典值类型的可选值。例如,对于存储String
值的字典,该方法返回类型为String?
或“ optional String
”的值。如果更新之前存在一个密钥,或者nil
不存在任何值,则此可选值包含该密钥的旧值:
- if let oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") {
- print("The old value for DUB was \(oldValue).")
- }
- // Prints "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 isn't in the airports dictionary.")
- }
- // Prints "The name of the airport is Dublin Airport."
您可以使用下标语法通过nil
为该键分配一个值来从字典中删除键值对:
- airports["APL"] = "Apple International"
- // "Apple International" isn't the real airport for APL, so delete it
- airports["APL"] = nil
- // APL has now been removed from the dictionary
或者,使用removeValue(forKey:)
方法从字典中删除键/值对。此方法删除键值对(如果存在)并返回删除的值,或者nil
如果不存在值则返回:
- if let removedValue = airports.removeValue(forKey: "DUB") {
- print("The removed airport's name is \(removedValue).")
- } else {
- print("The airports dictionary doesn't contain a value for DUB.")
- }
- // Prints "The removed airport's name is Dublin Airport."
遍历字典
您可以用字典遍历键值对for
-in
环。字典中的每个项目都作为一个元组返回,并且您可以在迭代过程中将元组的成员分解为临时常量或变量:(key, value)
- for (airportCode, airportName) in airports {
- print("\(airportCode): \(airportName)")
- }
- // LHR: London Heathrow
- // YYZ: Toronto Pearson
欲了解更多有关for
-in
循环,见为,在循环中。
您还可以通过访问字典的键或值的keys
和values
属性来检索其可迭代集合:
- for airportCode in airports.keys {
- print("Airport code: \(airportCode)")
- }
- // Airport code: LHR
- // Airport code: YYZ
- for airportName in airports.values {
- print("Airport name: \(airportName)")
- }
- // Airport name: London Heathrow
- // Airport name: Toronto Pearson
如果您需要通过带有Array
实例的API使用字典的键或值,请使用keys
或values
属性初始化一个新数组:
- let airportCodes = [String](airports.keys)
- // airportCodes is ["LHR", "YYZ"]
- let airportNames = [String](airports.values)
- // airportNames is ["London Heathrow", "Toronto Pearson"]
Swift的Dictionary
类型没有定义的顺序。要以特定顺序遍历字典的键或值,请使用sorted()
其keys
或values
属性上的方法。