首页 > 技术文章 > Swift5.4 语言指南(七) 集合类型

strengthen 2018-09-29 12:39 原文

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/9723114.html 
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

热烈欢迎,请直接点击!!!

进入博主App Store主页,下载使用各个作品!!!

注:博主将坚持每月上线一个新app!!!

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

../_images/CollectionTypes_intro_2x.png

Swift中的数组,集合和字典始终清楚它们可以存储的值和键的类型。这意味着您不能将错误类型的值错误地插入到集合中。这也意味着您可以对将从集合中检索到的值的类型充满信心。

笔记

Swift的数组,集合和字典类型被实现为泛型集合有关泛型类型和集合的更多信息,请参见泛型

集合的可变性

如果创建数组,集合或字典,并将其分配给变量,则创建的集合将是mutable这意味着您可以在创建集合后通过添加,删除或更改集合中的项目来更改(或变异)集合。如果将数组,集合或字典分配给常量,则该集合是不可变的,并且其大小和内容无法更改。

笔记

在不需要更改集合的所有情况下,创建不可变的集合是一个好习惯。这样做可以使您更轻松地推理代码,并使Swift编译器可以优化创建的集合的性能。

数组

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

笔记

Swift的Array类型被桥接到Foundation的NSArray类。

有关Array与Foundation和Cocoa一起使用的更多信息,请参见Array和NSArray之间的桥接

数组类型简写语法

Swift数组的类型完整写为Array<Element>,其中Element是允许存储数组的值的类型。您也可以将简写形式的数组类型编写为[Element]尽管这两种形式在功能上是相同的,但速写形式是首选,在引用数组的类型时,本指南通篇使用速记形式。

创建一个空数组

您可以使用初始化语法创建某种类型的空数组:

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

请注意,someInts变量[Int]的类型是从初始化程序的类型推断出来的。

或者,如果上下文已经提供了类型信息,例如函数参数或已经键入的变量或常量,则可以创建一个空数组,其中包含一个空数组文字,写为[](一个空的方括号对):

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

创建具有默认值的数组

Swift的Array类型还提供了一个初始化程序,用于创建一个特定大小的数组,并将其所有值都设置为相同的默认值。您向初始化程序传递了适当类型的默认值(称为repeating):以及该值在新数组中重复的次数(称为count):

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

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

您可以使用加法运算符(+将两个具有兼容类型的现有数组加在一起,从而创建一个新数组从添加到一起的两个数组的类型可以推断出新数组的类型:

  1. var anotherThreeDoubles = Array(repeating: 2.5, count: 3)
  2. // anotherThreeDoubles is of type [Double], and equals [2.5, 2.5, 2.5]
  3. var sixDoubles = threeDoubles + anotherThreeDoubles
  4. // sixDoubles is inferred as [Double], and equals [0.0, 0.0, 0.0, 2.5, 2.5, 2.5]

使用数组文字创建数组

您还可以使用数组常量初始化数组,这是将一个或多个值写为数组集合的一种简便方法。数组文字被写为一个值列表,以逗号分隔,并用一对方括号括起来:

  1. [value 1, value 2, value 3]

下面的示例创建一个名为shoppingList存储String的数组

  1. var shoppingList: [String] = ["Eggs", "Milk"]
  2. // 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可能以较短的形式写成:

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

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

访问和修改数组

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

要找出数组中的项目数,请检查其只读count属性:

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

使用BooleanisEmpty属性作为检查该count属性是否等于的快捷方式0

  1. if shoppingList.isEmpty {
  2. print("The shopping list is empty.")
  3. } else {
  4. print("The shopping list isn't empty.")
  5. }
  6. // Prints "The shopping list isn't empty."

您可以通过调用数组的append(_:)方法将新项目添加到数组的末尾

  1. shoppingList.append("Flour")
  2. // shoppingList now contains 3 items, and someone is making pancakes

或者,在附加赋值运算符(+=后面附加一个或多个兼容项的数组

  1. shoppingList += ["Baking Powder"]
  2. // shoppingList now contains 4 items
  3. shoppingList += ["Chocolate Spread", "Cheese", "Butter"]
  4. // shoppingList now contains 7 items

通过使用下标语法从数组中检索一个值,在数组名称后紧接着在方括号内传递要检索的值的索引:

  1. var firstItem = shoppingList[0]
  2. // firstItem is equal to "Eggs"

笔记

数组中的第一项索引为0,而不是1Swift中的数组始终为零索引。

您可以使用下标语法来更改给定索引处的现有值:

  1. shoppingList[0] = "Six eggs"
  2. // 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"

  1. shoppingList[4...6] = ["Bananas", "Apples"]
  2. // shoppingList now contains 6 items

要将项目以指定的索引插入数组,请调用数组的insert(_:at:)方法:

  1. shoppingList.insert("Maple Syrup", at: 0)
  2. // shoppingList now contains 7 items
  3. // "Maple Syrup" is now the first item in the list

对该insert(_:at:)方法的调用会在购物清单的最开头插入一个值为的新商品,该商品的索引为"Maple Syrup"0

同样,您可以使用remove(at:)方法从数组中删除一个项目此方法将删除指定索引处的项目并返回已删除的项目(尽管如果不需要,您可以忽略返回的值):

  1. let mapleSyrup = shoppingList.remove(at: 0)
  2. // the item that was at index 0 has just been removed
  3. // shoppingList now contains 6 items, and no Maple Syrup
  4. // the mapleSyrup constant is now equal to the removed "Maple Syrup" string

笔记

如果尝试访问或修改数组现有范围之外的索引值,则会触发运行时错误。您可以通过将索引与数组的count属性进行比较来检查索引是否有效在阵列中的最大有效的索引是因为数组是从零然而索引,当(意味着数组为空),没有有效的索引。count 1count0

删除项目后,数组中的所有间隙都将关闭,因此index的值0再次等于"Six eggs"

  1. firstItem = shoppingList[0]
  2. // firstItem is now equal to "Six eggs"

如果要从数组中删除最后一项,请使用removeLast()方法而不是remove(at:)方法来避免查询数组的count属性。remove(at:)方法一样removeLast()返回删除的项目:

  1. let apples = shoppingList.removeLast()
  2. // the last item in the array has just been removed
  3. // shoppingList now contains 5 items, and no apples
  4. // the apples constant is now equal to the removed "Apples" string

遍历数组

可以遍历整个集合值与数组for-in循环:

  1. for item in shoppingList {
  2. print(item)
  3. }
  4. // Six eggs
  5. // Milk
  6. // Flour
  7. // Baking Powder
  8. // Bananas

如果需要每个项目的整数索引及其值,请使用enumerated()方法来遍历数组。对于数组中的每个项目,该enumerated()方法都返回一个由整数和该项目组成的元组。整数从零开始,每一项加一。如果您对整个数组进行枚举,则这些整数与项的索引匹配。您可以在迭代过程中将元组分解为临时常量或变量:

  1. for (index, value) in shoppingList.enumerated() {
  2. print("Item \(index + 1): \(value)")
  3. }
  4. // Item 1: Six eggs
  5. // Item 2: Milk
  6. // Item 3: Flour
  7. // Item 4: Baking Powder
  8. // Item 5: Bananas

欲了解更多有关for-in循环,见为,在循环中

存储不同集合中没有定义排序的相同类型的值。当项目的顺序不重要时,或者需要确保某个项目仅出现一次时,可以使用集合而不是数组。

笔记

Swift的Set类型被桥接到Foundation的NSSet类。

有关Set与Foundation和Cocoa一起使用的更多信息,请参见Set和NSSet之间的桥接

集合类型的哈希值

类型必须是可哈希的才能存储在集合中,也就是说,该类型必须提供一种为其自身计算哈希值的方法。哈希值是Int对于相等比较的所有对象都相同值,例如,如果,则的哈希值等于的哈希值== bab

所有斯威夫特的基本类型(例如StringIntDouble,和Bool)默认情况下可哈希,并可以作为设定值类型或字典密钥类型。默认情况下,没有关联值的枚举案例值(如Enumerations中所述)也是可哈希的。

笔记

通过使它们符合HashableSwift标准库中协议,可以将自己的自定义类型用作设置值类型或字典键类型有关实现所需hash(into:)方法的信息,请参见Hashable有关符合协议的信息,请参阅协议

设置类型语法

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

创建和初始化一个空集

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

  1. var letters = Set<Character>()
  2. print("letters is of type Set<Character> with \(letters.count) items.")
  3. // Prints "letters is of type Set<Character> with 0 items."

笔记

的类型的letters变量被推断为Set<Character>,从初始的类型。

或者,如果上下文已经提供了类型信息,例如函数参数或已经键入的变量或常量,则可以使用空数组文字创建一个空集:

  1. letters.insert("a")
  2. // letters now contains 1 value of type Character
  3. letters = []
  4. // letters is now an empty set, but is still of type Set<Character>

使用数组文字创建集合

您还可以使用数组文字初始化集合,这是将一个或多个值写为集合的一种简便方法。

下面的示例创建一个名为favoriteGenres存储String的集合

  1. var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
  2. // 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可能以较短的形式写成:

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

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

访问和修改集合

您可以通过集合的方法和属性来访问和修改集合。

要找出集合中的项目数,请检查其只读count属性:

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

使用BooleanisEmpty属性作为检查该count属性是否等于的快捷方式0

  1. if favoriteGenres.isEmpty {
  2. print("As far as music goes, I'm not picky.")
  3. } else {
  4. print("I have particular music preferences.")
  5. }
  6. // Prints "I have particular music preferences."

您可以通过调用集合的insert(_:)方法将新项目添加到集合中

  1. favoriteGenres.insert("Jazz")
  2. // favoriteGenres now contains 4 items

您可以通过调用集合的remove(_:)方法从集合中删除项目,该方法将删除该项目(如果它是集合的成员),并返回删除的值,或者nil如果集合中不包含该值,返回该值或者,可以使用其removeAll()方法删除集合中的所有项目

  1. if let removedGenre = favoriteGenres.remove("Rock") {
  2. print("\(removedGenre)? I'm over it.")
  3. } else {
  4. print("I never much cared for that.")
  5. }
  6. // Prints "Rock? I'm over it."

若要检查集合是否包含特定项目,请使用contains(_:)方法。

  1. if favoriteGenres.contains("Funk") {
  2. print("I get up on the good foot.")
  3. } else {
  4. print("It's too funky in here.")
  5. }
  6. // Prints "It's too funky in here."

遍历一组

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

  1. for genre in favoriteGenres {
  2. print("\(genre)")
  3. }
  4. // Classical
  5. // Jazz
  6. // Hip hop

欲了解更多有关for-in循环,见为,在循环中

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

  1. for genre in favoriteGenres.sorted() {
  2. print("\(genre)")
  3. }
  4. // Classical
  5. // Hip hop
  6. // Jazz

执行集合操作

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

基本设置操作

下图描绘了两个集合-ab-,其中各个集合操作的结果由阴影区域表示。

../_images/setVennDiagram_2x.png
  • 使用该intersection(_:)方法创建仅具有两个集合共有的值的新集合。
  • 使用该symmetricDifference(_:)方法创建一个新集合,其中任一集合中都有值,但不能同时包含两者。
  • 使用该union(_:)方法创建一个包含两个集合中所有值的新集合。
  • 使用该subtracting(_:)方法创建一个新集合,其值不在指定集合中。
  1. let oddDigits: Set = [1, 3, 5, 7, 9]
  2. let evenDigits: Set = [0, 2, 4, 6, 8]
  3. let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]
  4. oddDigits.union(evenDigits).sorted()
  5. // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  6. oddDigits.intersection(evenDigits).sorted()
  7. // []
  8. oddDigits.subtracting(singleDigitPrimeNumbers).sorted()
  9. // [1, 9]
  10. oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted()
  11. // [1, 2, 9]

设置成员资格和平等

下图描述了和的三个集合ab其中c重叠的区域表示集合之间共享的元素。Setasetb,因为它a包含中的所有元素b相反,setbseta,因为inb中的所有元素也包含在中ab和集c彼此不相交,因为它们没有共同的元素。

../_images/setEulerDiagram_2x.png
  • 使用“等于”运算符(==)确定两组是否包含所有相同的值。
  • 使用该isSubset(of:)方法确定集合中的所有值是否都包含在指定集合中。
  • 使用该isSuperset(of:)方法确定集合是否包含指定集合中的所有值。
  • 使用isStrictSubset(of:)isStrictSuperset(of:)方法来确定集合是子集还是超集,但不等于指定的集。
  • 使用该isDisjoint(with:)方法确定两个集合是否没有共同的值。