集合(Sets)
集合用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以使用集合而不是数组。
注意 Swift 的
Set
类型被桥接到 Foundation 中的NSSet
类。关于使用 Foundation 和 Cocoa 中
Set
的知识,参见 Bridging Between Set and NSSet
集合类型的哈希值
一个类型为了存储在集合中,该类型必须是可哈希化的——也就是说,该类型必须提供一个方法来计算它的 哈希值 。一个哈希值是 Int
类型的,相等的对象哈希值必须相同,比如 a == b
,因此必须 a.hashValue == b.hashValue
。
Swift 的所有基本类型(比如 String
、Int
、Double
和 Bool
)默认都是可哈希化的,可以作为集合值的类型或者字典键的类型。没有关联值的枚举成员值(在 枚举 有讲述)默认也是可哈希化的。
注意
你可以使用自定义的类型作为集合值的类型或者是字典键的类型,但需要使自定义类型遵循 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>
,这里的 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
类型,所以它只允许存储 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.") // 打印“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()
方法删除所有元素。
iflet 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
集合操作
你可以高效地完成集合的一些基本操作,比如把两个集合组合到一起,判断两个集合共有元素,或者判断两个集合是否全包含,部分包含或者不相交。
基本集合操作
下面的插图描述了两个集合 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
,以及通过重叠区域表述集合间共享的元素。集合 a
是集合 b
的 父集合 ,因为 a
包含了 b
中所有的元素。相反的,集合 b
是集合 a
的 子集合 ,因为属于 b
的元素也被 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