Swift5.x-集合类型(中文文档)

引言

继续学习Swift文档,从上一章节:字符串和字符,我们学习了Swift的字符串和字符相关的内容。集合这个数据结构在我们项目开发过程中,也会频繁被用到,所以需要熟练掌握它的用法。现在,我们开始学习Swift的集合相关的内容。由于篇幅较长,这里分篇来记录,接下来,开始吧!

如果你已经熟悉并掌握了集合的相关内容,那么请阅读下一章节:控制流

集合类型

Swift提供了三种主要的集合类型,即数组、集合和字典,用于存储值的集合。数组是值的有序集合。集合是惟一值的无序集合。字典是键-值关联的无序集合。


image.png

Swift中的数组、集合和字典总是清楚地知道它们可以存储的值和键的类型。这意味着不能将错误类型的值插入到集合中。这还意味着可以明确知道从集合中检索的值的类型。

注意
Swift的数组、set和dictionary类型被实现为泛型集合。有关泛型类型和集合的更多信息,请参见泛型

1 可变集合

如果您创建了一个数组、一个集合或一个字典,并将其分配给一个变量,那么所创建的集合将是可变的。这意味着您可以在集合创建后通过添加、删除或更改集合中的项来更改(或改变)集合。如果将数组、集合或字典分配给常量,则该集合是不可变的,其大小和内容不能更改。

注意
在所有集合不需要更改的情况下,创建不可变集合是一个很好的实践。这样做可以使您更容易地推理您的代码,并使Swift编译器能够优化您创建的集合的性能。

2 数组

数组将相同类型的值存储在有序列表中。相同的值可以在数组的不同位置出现多次。

注意
Swift的数组类型被桥接到Foundation的NSArray类。

有关在Foundation和Cocoa中使用数组的更多信息,请参见Array和NSArray之间的桥接

2.1 数组类型简写语法

Swift数组的类型被写成array <Element>,其中Element是数组允许存储的值的类型。还可以将数组的类型简写为[Element]。尽管这两种形式在功能上是相同的,但是首选的是简写形式,并且在本指南中提到数组的类型时都会使用简写形式。

2.2 创建空数组

你可以使用初始化语法创建一个特定类型的空数组:

var someInts = [Int]()
print("someInts is of type [Int] with \(someInts.count) items.")
// Prints "someInts is of type [Int] with 0 items."

或者,如果上下文已经提供了类型信息,比如一个函数参数或一个已经类型化的变量或常量,你可以用一个空数组文字创建一个空数组,它写为:

someInts.append(3)
// someInts now contains 1 value of type Int
someInts = []
// someInts is now an empty array, but is still of type [Int]

2.3 使用默认值创建数组

Swift的数组类型还提供了一个初始化器,用于创建一个特定大小的数组,数组的所有值都设置为相同的默认值。你给初始化器传递一个适当类型的默认值(称为repeat):和该值在新数组中重复的次数(称为count):

var threeDoubles = Array(repeating: 0.0, count: 3)
// threeDoubles is of type [Double], and equals [0.0, 0.0, 0.0]

2.4 通过将两个数组加在一起创建数组

通过使用加法运算符(+)将两个具有兼容类型的现有数组相加,可以创建新的数组。新数组的类型是从你加在一起的两个数组的类型推断出来的:

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]

2.5 用数组文字创建数组

还可以用数组文字初始化数组,这是将一个或多个值写入数组集合的一种速记方法。一个数组文字被写为一个值的列表,用逗号分隔,由一对方括号包围:

[value 1, value 2, value 3]

下面的例子创建了一个名为shoppingList的数组来存储字符串值:

var shoppingList: [String] = ["Eggs", "Milk"]
// shoppingList has been initialized with two initial items

shoppingList变量被声明为“字符串值数组”,写为[string]。因为这个特定数组指定了字符串的值类型,所以只允许存储字符串值。在这里,shoppingList数组用两个字符串值(“Eggs”和“Milk”)初始化,用数组文字编写。

注意
shoppingList数组被声明为一个变量(使用var导入器),而不是一个常量(使用let导入器),因为在下面的示例中有更多的条目被添加到购物列表中。

在本例中,数组文字只包含两个字符串值。这与shoppingList变量声明的类型(只能包含字符串值的数组)相匹配,因此允许数组文字的赋值作为用两个初始项初始化shoppingList的一种方式。

由于Swift的类型推断,如果您使用包含相同类型值的数组文本初始化数组,则不必编写数组的类型。shoppingList的初始化可以写成更短的形式:

var shoppingList = ["Eggs", "Milk"]

因为数组文字中的所有值都是相同类型的,Swift可以推断[String]是用于shoppingList变量的正确类型。

2.6 访问和修改数组

可以通过数组的方法和属性或下标语法访问和修改数组。

要找出数组中的项数,请检查它的只读计数属性:

print("The shopping list contains \(shoppingList.count) items.")
// Prints "The shopping list contains 2 items."

使用布尔属性isEmpty作为检查计数属性是否等于0的快捷方式:

if shoppingList.isEmpty {
    print("The shopping list is empty.")
} else {
    print("The shopping list is not empty.")
}
// Prints "The shopping list is not 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]。试图将一个项追加到数组的末尾会导致运行时错误。

您还可以使用下标语法一次性更改一个值范围,即使替换值集的长度与要替换的范围不同。下面的例子将“巧克力酱”、“奶酪”和“黄油”替换为“香蕉”和“苹果”:

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,因为数组的索引是从0开始的。但是,当count为0(意味着数组为空)时,就没有有效索引了。

当移除一个元素时,数组中的任何间隙都被关闭,因此索引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

2.7 遍历数组

你可以用for-in循环遍历数组中的所有值:

for item in shoppingList {
    print(item)
}
// Six eggs
// Milk
// Flour
// Baking Powder
// Bananas

如果需要每个项的整数索引及其值,则使用枚举()方法迭代数组。对于数组中的每个项,枚举()方法返回由整数和项组成的元组。整数从0开始,每一项加1;如果枚举整个数组,则这些整数与项的索引匹配。您可以分解元组为临时常量或变量作为迭代的一部分:

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循环的更多信息,请参见For-in循环

3 集合

集合在没有定义顺序的集合中存储相同类型的不同值。如果项的顺序不重要,或者需要确保项只出现一次,则可以使用集合而不是数组。

注意
Swift的Set类型被桥接到Foundation的NSSet类。
有关在Foundation和Cocoa中使用Set的更多信息,请参见Set和NSSet之间的桥接

3.1 集合类型的散列值(hash)

为了存储在集合中,类型必须是可hashable的——也就是说,类型必须提供一种为自身计算哈希值的方法。散列值是一个整型值,它对所有相等比较的对象都是相同的,这样,如果A == b,就是A.hashValue == b.hashValue。

默认情况下,Swift的所有基本类型(比如字符串、Int、Double和Bool)都是hashable的,可以用作设置值类型或字典键类型。默认情况下,没有关联值(如枚举中所述)的枚举用例值也是可hashable的。

注意
通过使它们符合Swift标准库中的Hashable协议,您可以使用自己的自定义类型作为设置值类型或字典键类型。符合Hashable协议的类型必须提供一个可获取的Int属性hashValue。类型的hashValue属性返回的值不必在同一程序的不同执行中或在不同程序中相同。

因为Hashable协议符合Equatable,符合的类型也必须提供equals操作符(==)的实现。可Equatable协议要求任何符合==的实现都是等价关系。也就是说,对于所有的a、b、c值,==的实现必须满足以下三个条件:

  • a == b(自反性)
  • a == b意味着b == a(对称)
  • a == b && b == c意味着a == c(传递性)

有关遵守协议的更多信息,请参见协议

3.2 集合类型语法

Swift集合的类型写为set <Element>,其中Element是该集合允许存储的类型。与数组不同,集合没有等价的简写形式。

3.3 创建和初始化空的集合

可以用初始化器创建特定类型的空集合

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."

注意
根据初始化器的类型,推断字母变量的类型设置为<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>

3.4 用数组文字创建一个集合

还可以用数组文字初始化set,这是将一个或多个值编写为set集合的一种速记方法。

下面的例子创建了一个名为favoritegenre的集合来存储字符串值:

var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
// favoriteGenres has been initialized with three initial items

favoritegenre变量被声明为“一组字符串值”,写成set <String>。因为这个特殊的集合指定了字符串的值类型,所以只允许存储字符串值。在这里,favoritegenre设置用三个字符串值(“Rock”、“Classical”和“Hip hop”)初始化,用一个数组文字编写。

注意
favoritegenre设置被声明为一个变量(用var导入器)而不是一个常量(用let导入器),因为在下面的例子中会添加和删除条目。

不能仅从数组文字推断集合类型,因此必须显式声明类型集。但是,由于Swift的类型推断,如果使用只包含一种类型值的数组文字初始化集合,则不必编写集合元素的类型。初始化的favoritegenre可以写在一个更短的形式代替:

var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]

因为数组文字中的所有值都是相同类型的,Swift可以推断Set<String>是favoritegenre变量使用的正确类型。

3.5 访问和修改集合

通过集合的方法和属性访问和修改集合。

要找出一个集合中的项数,请检查它的只读计数属性:

print("I have \(favoriteGenres.count) favorite music genres.")
// Prints "I have 3 favorite music genres."

使用布尔属性isEmpty作为检查计数属性是否等于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."

3.6 迭代集合

可以使用for-in循环遍历集合中的值。

for genre in favoriteGenres {
    print("\(genre)")
}
// Classical
// Jazz
// Hip hop

有关For- in循环的更多信息,请参见For-In Loops

Swift的Set类型没有定义的顺序。要按特定顺序遍历集合的值,可以使用sorted()方法,该方法将集合的元素作为数组返回,并使用<操作符排序。

for genre in favoriteGenres.sorted() {
    print("\(genre)")
}
// Classical
// Hip hop
// Jazz

4 执行集合操作

您可以高效地执行基本的集合操作,例如将两个集合组合在一起,确定两个集合有哪些相同的值,或者确定两个集合是否包含所有相同的值、一些值或不包含相同的值。

4.1 基本设置操作

下图描述了两个集合(a和b),它们是由阴影区域表示的各种集合操作的结果。


image.png
  • 使用交集(_:)方法创建一个新集合,其中只有两个集合通用的值。
  • 使用symmetricDifference(_:)方法创建一个新的集合,该集合的值在两个集合中都有,但不能同时在两个集合中都有。
  • 使用union(_:)方法创建一个包含两个集合中的所有值的新集合。
  • 使用减法(_:)方法创建一个新的集合,其值不在指定的集合中。
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]

4.2 集合关系和相等

下图描绘了三个集合(a,b和c),其中重叠的区域表示集合之间共享的元素。 集a是集b的超集,因为a包含b中的所有元素。 相反,集合b是集合a的子集,因为b中的所有元素也都包含在a中。 集b和集c彼此不交集,因为它们没有共同的元素。

image
  • 使用is equal运算符(==)判断两个集合是否包含一样的元素。
  • 使用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

5 字典

字典在没有定义顺序的情况下将相同类型的键和相同类型的值之间的关联存储在集合中。 每个值都与唯一键相关联,该键充当字典中该值的标识符。 与数组中的项目不同,字典中的项目没有指定的顺序。 当您需要根据其标识符查找值时,可以使用字典,这与使用现实世界词典查找特定单词的定义的方式几乎相同。

注意
Swift的“字典”类型桥接到Foundation的NSDictionary类。

有关在Foundation和Cocoa中使用Dictionary的更多信息,请参见在Dictionary和NSDictionary之间架桥。

5.1 字典类型简写

Swift字典的类型全写为Dictionary <Key,Value>,其中Key是可用作字典键的值的类型,Value是字典为这些键存储的值的类型。

注意
字典键类型必须符合哈希协议,例如集合的值类型。

您还可以将简写形式的字典类型写为[Key:Value]。 尽管这两种形式在功能上是相同的,但速记形式是首选,在引用字典类型时在本指南中使用。

5.2 创建空字典

和数组一样,可以用初始化语法创建一个特性类型的空字典:

var namesOfIntegers = [Int: String]()
// namesOfIntegers is an empty [Int: String] dictionary

上面例子创建一个[Int: String]类型的字典,key的类型是Int,value的类型是String

如果上下文已经提供了类型信息,则可以使用空字典文字创建一个空字典,该文字以[:]书写(一对方括号内的冒号):

namesOfIntegers[16] = "sixteen"
// namesOfIntegers now contains 1 key-value pair
namesOfIntegers = [:]
// namesOfIntegers is once again an empty dictionary of type [Int: String]

5.3 用字典文字创建字典

您还可以使用字典文字来初始化字典,该字典文字的语法与前面看到的数组文字相似。 字典文字是将一个或多个键值对编写为字典集合的一种简便方法。

key-value是键和值的组合。 在字典文字中,每个键值对中的键和值都用冒号分隔。 键值对以列表形式写入,并用逗号分隔,并用方括号包围:

[key 1: value 1, key 2: value 2, key 3: value 3]

下面的示例创建一个字典来存储国际机场的名称。 在此字典中,键是三个字母的国际航空运输协会代码,值是机场名称:

var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]

airports被声明为一个[String: String]类型的字典,意味着字典的key是String类型,value也是String类型。

注意
airports被声明为一个变量(用var修饰),而不是常量(用let修饰),因为在下面的例子中需要新增数据进去。

airports用字典文字被初始化为包含两个键值对的字典。第一个key值是“YYZ”,value值是“Toronto Pearson”。第二个key值是“DUB”,value值是“Dublin”。

这个字典包含两个String: String键值对,键值对的类型需要匹配airports声明时的类型(key和value只能是String类型),因此,允许使用字典文字进行分配,以作为初始化带有两个初始项的airports字典的一种方式。

与数组一样,如果使用其键和值具有一致类型的字典文字来初始化字典,则不必编写字典的类型。 airports的初始化本来可以写成较短的形式:

var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]

因为文字中的所有键彼此都具有相同的类型,并且所有值也都具有相同的类型,所以Swift可以推断[String:String]是用于airports字典的正确类型。

5.4 访问和修改字典

您可以通过其方法和属性或使用下标语法来访问和修改字典。

与数组一样,您可以通过检查字典的只读count属性来找出字典中的项目数:

print("The airports dictionary contains \(airports.count) items.")
// Prints "The airports dictionary contains 2 items."

使用布尔值isEmpty属性作为检查count属性是否等于0的快捷方式:

if airports.isEmpty {
    print("The airports dictionary is empty.")
} else {
    print("The airports dictionary is not empty.")
}
// Prints "The airports dictionary is not 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?或“可选的字符串”的值。 如果更新之前存在一个旧值,则此可选值包含该键的旧值;如果不存在任何值,则为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 is not in the airports dictionary.")
}
// Prints "The name of the airport is Dublin Airport."

您可以使用下标语法通过为该键分配nil值来从字典中删除键/值对:

airports["APL"] = "Apple International"
// "Apple International" is not 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 does not contain a value for DUB.")
}
// Prints "The removed airport's name is Dublin Airport."

5.5 遍历字典

您可以使用for-in循环遍历字典中的键/值对。 字典中的每一项都作为(键,值)元组返回,并且您可以在迭代过程中将元组的成员分解为临时常量或变量:

for (airportCode, airportName) in airports {
    print("\(airportCode): \(airportName)")
}
// LHR: London Heathrow
// YYZ: Toronto Pearson

关于更多For-in循环,请参见For-In Loops

您还可以通过访问字典的键或值属性来检索字典的键或值的可迭代集合:

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()方法。

总结

这篇文章主要涉及到集合的相关内容:包括数组,集合和字典。集合的初始化、访问和修改以及遍历,这些是集合的基本操作,通过阅读上面的内容,相信大家对它们会有个熟悉的印象;配合代码编写,您将会有更深的印象。集合这个数据结构在我们项目开发过程中,也会频繁被用到,所以需要熟练掌握它的用法。

最后,喜欢的朋友可以给个👍哦,你的鼓励就是我的动力,嘿嘿~

上一章节:字符串和字符

下一章节:控制流

参考文档: Swift - Collection Types

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容