5.4. 集合(Sets)

集合用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以使用集合而不是数组。

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

关于使用 Foundation 和 Cocoa 中 Set 的知识,参见 Bridging Between Set and NSSet

集合类型的哈希值

一个类型为了存储在集合中,该类型必须是可哈希化的——也就是说,该类型必须提供一个方法来计算它的哈希值。一个哈希值是 Int 类型的,相等的对象哈希值必须相同,比如 a == b,因此必须 a.hashValue == b.hashValue

Swift 的所有基本类型(比如 StringIntDoubleBool)默认都是可哈希化的,可以作为集合值的类型或者字典键的类型。没有关联值的枚举成员值(在 枚举 有讲述)默认也是可哈希化的。

注意

你可以使用自定义的类型作为集合值的类型或者是字典键的类型,但需要使自定义类型遵循 Swift 标准库中的 Hashable 协议。遵循 Hashable 协议的类型需要提供一个类型为 Int 的可读属性 hashValue。由类型的 hashValue 属性返回的值不需要在同一程序的不同执行周期或者不同程序之间保持相同。

因为 Hashable 协议遵循 Equatable 协议,所以遵循该协议的类型也必须提供一个“是否相等”运算符(==)的实现。这个 Equatable 协议要求任何遵循 == 实现的实例间都是一种相等的关系。也就是说,对于 a,b,c 三个值来说,== 的实现必须满足下面三种情况:

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

关于遵循协议的更多信息,请看 协议

集合类型语法

Swift 中的集合类型被写为 Set,这里的 Element 表示集合中允许存储的类型。和数组不同的是,集合没有等价的简化形式。

创建和构造一个空的集合

你可以通过构造器语法创建一个特定类型的空集合

var letters = Set<Character>()
print("letters is of type Set<Character> with \(letters.count) items.")
// 打印“letters is of type Set<Character> with 0 items.”

注意:通过构造器,这里 letters 变量的类型被推断为 Set<Character>

此外,如果上下文提供了类型信息,比如作为函数的参数或者已知类型的变量或常量,你可以通过一个空的数组字面量创建一个空的集合

letters.insert("a")
// letters 现在含有1个 Character 类型的值
letters = []
// letters 现在是一个空的 Set,但是它依然是 Set<Character> 类型

用数组字面量创建集合

你可以使用数组字面量来构造集合,相当于一种简化的形式将一个或者多个值作为集合元素。

下面的例子创建一个称之为 favoriteGenres 的集合来存储 String 类型的值:

var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
// favoriteGenres 被构造成含有三个初始值的集合

这个 favoriteGenres 变量被声明为“一个 String 值的集合”,写为 Set。由于这个特定集合指定了值为 String 类型,所以它允许存储 String 类型值。这里的 favoriteGenres 变量有三个 String 类型的初始值("Rock""Classical""Hip hop"),以数组字面量的形式书写。

注意favoriteGenres 被声明为一个变量(拥有 var 标示符)而不是一个常量(拥有 let 标示符),因为它里面的元素将会在之后的例子中被增加或者移除。

一个集合类型不能从数组字面量中被直接推断出来,因此 Set 类型必须显式声明。然而,由于 Swift 的类型推断功能,如果你想使用一个数组字面量构造一个集合并且与该数组字面量中的所有元素类型相同,那么无须写出集合的具体类型。favoriteGenres 的构造形式可以采用简化的方式代替:

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

由于数组字面量中的所有元素类型相同,Swift 可以推断出 Set 作为 favoriteGenres 变量的正确类型。

访问和修改一个集合

你可以通过集合的属性和方法来对其进行访问和修改。为了获取一个集合中元素的数量,可以使用其只读属性 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.”

你可以通过调用集合的 insert(_:) 方法来添加一个新元素:

favoriteGenres.insert("Jazz")
// favoriteGenres 现在包含4个元素

你可以通过调用集合的 remove(_:) 方法去删除一个元素,如果它是该集合的一个元素则删除它并且返回它的值,若该集合不包含它,则返回 nil。另外,集合可以通过 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(_:) 方法去检查集合中是否包含一个特定的值:

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 循环中遍历一个集合中的所有值。

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

更多关于 for-in 循环的信息,参见 For 循环

Swift 的 Set 类型没有确定的顺序,为了按照特定顺序来遍历一个集合中的值可以使用 sorted() 方法,它将返回一个有序数组,这个数组的元素排列顺序由操作符 < 对元素进行比较的结果来确定。

for genre in favoriteGenres.sorted() {
    print("\(genre)")
}
// Classical
// Hip hop
// Jazz
下一节:你可以高效地完成集合的一些基本操作,比如把两个集合组合到一起,判断两个集合共有元素,或者判断两个集合是否全包含,部分包含或者不相交。