4 Collection Types 集合类型

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

CollectionTypes_intro_2x.png

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

Mutability of Collections 可变性

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

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

Arrays 数组

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

Array Type Shorthand Syntax 数组类型简写语法

Swift数组的类型被完整地写成array ,其中Element是数组允许存储的值的类型。您还可以将数组的类型以简写形式写成[Element]。虽然这两种形式在功能上是相同的,但是在整个教程指南中,当引用数组的类型时,首选使用简写形式。

Creating an Empty Array 创建一个空数组

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

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]

Creating an Array with a Default Value 创建具有默认值的数组

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

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

Creating an Array by Adding Two Arrays Together 通过两个数组加起来创建新数组

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

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]

Creating an Array with an Array Literal 使用数组字面量创建数组

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

[value1, value2, value3]

下面的例子创建了一个名为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变量的正确类型。

Accessing and Modifying an Array 访问和修改数组

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

若要查明数组中的项数,请检查其只读计数属性:

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

使用Boolean isEmpty属性作为检查count属性是否等于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.count] = "Salt"尝试将一个项追加到数组末尾会导致运行时错误。

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

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

这里是将4-6三个元素替换成2个元素了

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

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

类似地,使用remove(at:)方法从数组中删除项。此方法删除指定索引处的项并返回已删除的项(如果不需要,可以忽略返回值):

let mapleSyrup = shoppingList.remove(at: 0)

注意:如果试图访问或修改超出数组现有边界的索引值,则会触发运行时错误。通过将索引与数组的count属性进行比较,可以在使用索引之前检查索引是否有效。数组中最大的有效索引是count - 1,因为数组是从0开始索引的。

如果希望从数组中删除最后一项,请使用removeLast()方法而不是remove(at:)方法,以避免查询数组的count属性。与remove(at:)方法类似,removeLast()返回已删除的项:

let apples = shoppingList.removeLast()

Iterating Over an Array 遍历数组

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

for item in shoppingList {
    print(item)
}

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

for (index, value) in shoppingList.enumerated() {
    print("Item \(index + 1): \(value)")
}

Sets 集合

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

Hash Values for Set Types set类型的哈希值

一种类型必须是可hashable的,以便存储在一个集合中——也就是说,类型必须提供一种为自己计算哈希值的方法。哈希值是一个Int值,它对于所有比较相同的对象都是相同的,因此,如果a == b,那么它就遵循a.hashValue = = b.hashValue。

Swift的所有基本类型(如String、Int、Double和Bool)在默认情况下都是可hashable的,可以用作set value类型或dictionary key类型。没有关联值的枚举用例值(如枚举中所述)在默认情况下也是可hashable的。

您可以使用自己的自定义类型作为set value类型或dictionary key类型,方法是使它们符合Swift标准库中的Hashable协议。符合Hashable协议的类型必须提供一个名为hashValue的可获取Int属性。类型的hashValue属性返回的值在同一程序的不同执行或不同程序中不需要相同。

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

  • a == a (Reflexivity)
  • a == b implies b == a (Symmetry)
  • a == b && b == c implies a == c (Transitivity)

Set Type Syntax set类型语法

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

Creating and Initializing an Empty Set 创建和初始化空集合set

创建一个元素类型的为Character 的空集合:

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.insert("a")
// letters now contains 1 value of type Character
letters = []
// letters is now an empty set, but is still of type Set<Character>

Creating a Set with an Array Literal 使用数组创建集合set

您还可以使用数组文字初始化一个集合,作为将一个或多个值写入集合的简写方法。
下面的例子创建了一个名为favoritegenre的集合来存储字符串值:

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

指定了set集合中存储类型为String

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

Swift 可以类型推断

Accessing and Modifying a Set 访问和修改set

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

元素个数

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

是否为空

if favoriteGenres.isEmpty {
    print("As far as music goes, I'm not picky.")
} else {
    print("I have particular music preferences.")
}

插入一个新元素

favoriteGenres.insert("Jazz")

移除元素

移除单个元素 如果集合中有改元素,返回该元素,没有返回nil
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."

移除所有元素
favoriteGenres.removeAll()

是否包含某个元素

if favoriteGenres.contains("Funk") {
    print("I get up on the good foot.")
} else {
    print("It's too funky in here.")
}

遍历set集合

可以使用for-in遍历set集合

for genre in favoriteGenres {
    print("\(genre)")
}

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

for genre in favoriteGenres.sorted() {
    print("\(genre)")
}

Performing Set Operations 执行set操作

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

Fundamental Set Operations 基本设置操作

下图描述了两个集合—a和b—以及用阴影区域表示的各种集合操作的结果。


setVennDiagram_2x.png
  • 使用 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]

Set Membership and Equality set成员关系和相等

下图描述了三个集合a、b和c,其中重叠的区域表示集合之间共享的元素。设置一个是集的超集b,因为包含了所有元素b。相反地,b是集的一个子集,因为所有元素b也包含一组。b, c是不相交的,因为他们没有共同之处。


setEulerDiagram_2x.png
  • 使用 == 确定两个set集合中元素是否完全相同。
    是返回true,否则返回false
  • 使用 isSubset(of:) 方法确定当前集合是否是另一个集合的子集(当前集合的所有元素在另一个集合中都可以找到)。
    是返回true,否则返回false
  • 使用 isSuperset(of:) 方法 确定当前集合是否是另一个集合的超集(当前集合中可以找到另一个集合的所有元素)。
    是返回true,否则返回false
  • 使用 isStrictSubset(of:) 方法来确定当前集合是否是另一个集合的真子集(是子集且不完全相等)。
    是返回true,否则返回false
  • 使用 isStrictSuperset(of:) 方法来确定当前集合是否是另一个集合的真超集(是超集且不完全相等)。
    是返回true,否则返回false
  • 使用 isDisjoint(with:) 方法来确定两个集合是否没有共同的元素。
    有相同元素返回false,没有返回true
let houseAnimals: Set = ["🐶", "🐱"]
let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"]
let cityAnimals: Set = ["🐦", "🐭"]

houseAnimals.isSubset(of: farmAnimals)
// true
farmAnimals.isSuperset(of: houseAnimals)
// true
farmAnimals.isDisjoint(with: cityAnimals)
// true

Dictionary 字典

字典在没有定义顺序的集合中存储相同类型的键和相同类型的值之间的关联。
每个value都与一个惟一的key相关联,该key在字典中充当该value的标识符。
与数组中的item不同,字典中的item没有指定的顺序。
当您需要根据值的标识符查找值时,您将使用字典,这与实际字典用于查找特定单词的定义非常相似。

Dictionary Type Shorthand Syntax 字典简写语法

Swift字典的类型完整地写成Dictionary<Key,Value> ,其中Key是字典中每项标识符值的类型,Value是字典为这些键存储的值的类型。

注意:字典的键类型必须符合Hashable协议,就像集合的值类型一样。

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

Creating an Empty 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 literal是将一个或多个键值对编写为dictionary集合的一种简写方法。

键值对是键和值的组合。在字典文本中,每个键值对中的键和值之间用冒号分隔。键值对以列表形式编写,用逗号分隔,用一对方括号括起来:

[key1: value1, key2: value2, key3: value3]

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

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

机场字典声明为具有[String: String]类型,这意味着“其键为String类型的字典,其值也为String类型的字典”。

机场字典被声明为一个变量(使用var关键字),而不是一个常量(使用let关键字),因为在下面的示例中,更多的机场被添加到字典中。

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

这个字典文本包含两个字符串:字符串对。此键-值类型与机场变量声明的类型相匹配(只有字符串键和字符串值的字典),因此允许分配字典字面量,以便使用两个初始项初始化机场字典。

与数组一样,如果要用一个字典字面量初始化字典,而字典字面量的键和值具有一致的类型,则不必编写字典的类型。机场的初始化可以写成更短的形式:

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

因为文字中的所有键都是彼此相同的类型,同样,所有值都是彼此相同的类型,所以Swift可以推断[String: String]是机场字典使用的正确类型。

Accessing and Modifying a Dictionary 访问和修改字典

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

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

print(airports.count)
输出2

判断是否为空字典

if airports.isEmpty {
    print("The airports dictionary is empty.")
} else {
    print("The airports dictionary is not empty.")
}

可以使用下标语法将新项添加到字典中。使用适当类型的新键作为下标索引,并分配适当类型的新值:

airports["LHR"] = "London"

您还可以使用下标语法来更改与特定键关联的值:

airports["LHR"] = "London Heathrow"

作为下标的替代方法,使用字典的updateValue(:forKey:)方法来设置或更新特定键的值。与上面的下标示例一样,updateValue(:forKey:)方法在一个键不存在的情况下为该键设置一个值,或者在该键已经存在的情况下更新该值。但是,与下标不同,updateValue(_:forKey:)方法在执行更新后返回旧值。这使您能够检查是否发生了更新。

updateValue(_:forKey:) 方法的作用是:返回值为可选类型,因为key可能存在,可能不存在,存在返回key所对应的旧值,不存在就返回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."

还可以使用下标语法从字典中检索特定键的值。因为可以请求一个没有值的键,所以dictionary的下标返回dictionary值类型的可选值。如果字典包含请求键的值,下标返回一个可选值,该值包含该键的现有值。否则,下标返回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."

Iterating Over a Dictionary 遍历字典

使用 for-in 遍历字典

for (airportCode, airportName) in airports {
    print("\(airportCode): \(airportName)")
}

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

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

如果你需要使用字典的键或值与一个API的数组实例,初始化一个新的数组与键或值的属性:

let airportCodes = [String](airports.keys)
// airportCodes is ["YYZ", "LHR"]

let airportNames = [String](airports.values)
// airportNames is ["Toronto Pearson", "London Heathrow"]

Swift的字典类型没有定义顺序。若要按特定顺序遍历字典的键或值,请对其键或值属性使用ordered()方法。

<<返回目录

推荐阅读更多精彩内容