首页 / Swift 编程语言教程 / 集合 Set

Swift 编程语言教程

集合 Set

本教程共 93 篇 · 第 18 篇 · 更新于 2026-08-08 · 约 8 分钟阅读

SwiftSwift 编程语言教程集合Set去重交集并集哈希

本节目标:读完你能用 Set 存不重复的值、判断是否包含某元素,并用交并差等运算处理集合关系。

数组关心”顺序和重复”,集合(Set)正相反:它存的是”不重复的值”,而且没有顺序概念。当你只关心”有没有”而不关心”第几个”时,Set 比数组更合适。这一章讲它的特性和运算。

1-1 Set 是什么

Set 是同一类型、无重复、无顺序的集合。同一元素放两次,Set 里只留一个。遍历 Set 时,元素出现的顺序不保证。

var genres: Set<String> = ["摇滚", "古典", "嘻哈"]

因为 Set 没有简写形式,类型 Set<元素> 必须显式写出(或至少标 Set)。不过元素类型能推断:

var genres: Set = ["摇滚", "古典", "嘻哈"]   // 推断为 Set<String>

1-2 为什么能去重:哈希

Set 要存不重复的值,前提是该类型”可哈希”(Hashable)。哈希简单说,就是类型能算出自己的一个整数特征值:相等的对象哈希值一定相等。

Swift 的基础类型(StringIntDoubleBool)默认都可哈希,能直接当 Set 的元素。无关联值的枚举也可哈希。想用自己的类型当元素,需要让它遵循 Hashable 协议——那是后面的话题。

正是靠哈希,Set 判断”是否已存在某值”极快,这是它去重和成员查询高效的底层原因。

1-3 创建空集合

用初始化语法造空 Set:

var letters = Set<Character>()

如果上下文已有类型信息,也能用空数组字面量 [] 清空,但类型保持不变:

letters.insert("a")
letters = []   // 清空,仍是 Set<Character>

1-4 增删与判空

加元素用 insert(_:)

genres.insert("爵士")   // 现在 4 项

删元素用 remove(_:),找到就删并返还原值,没有则返回 nilremoveAll() 清空:

if let removed = genres.remove("摇滚") {
    print("\(removed) 已移除")
}

count 看数量,isEmpty 判空,和数组一致:

if genres.isEmpty {
    print("没有偏好")
}

1-5 判断成员:contains

想知道 Set 里有没有某个值,用 contains(_:)

if genres.contains("放克") {
    print("跟着节奏摇")
} else {
    print("这里太不 funky 了")
}

比数组用 contains 一样,但 Set 的查询通常更快,因为底层基于哈希。

1-6 遍历集合

for-in 遍历,但顺序不保证:

for genre in genres {
    print(genre)
}

如果希望按固定顺序看,用 sorted() 返回一个排好序的数组:

for genre in genres.sorted() {
    print(genre)
}

sorted()< 排序,返回的已是数组,顺序确定,适合展示给用户。

1-7 集合运算:交并差

Set 最强大的地方是内置了数学集合运算。假设两组数字:

let odds: Set = [1, 3, 5, 7, 9]
let evens: Set = [0, 2, 4, 6, 8]
let primes: Set = [2, 3, 5, 7]

union(_:) 并集,两家合并去重:

odds.union(evens).sorted()   // [0,1,2,3,4,5,6,7,8,9]

intersection(_:) 交集,只留两家都有的:

odds.intersection(evens).sorted()   // []

subtracting(_:) 差集,留在 A 但不在 B 的:

odds.subtracting(primes).sorted()   // [1, 9]

symmetricDifference(_:) 对称差,留只在其中一家、不在两家的:

odds.symmetricDifference(primes).sorted()   // [1, 2, 9]

这些运算返回新集合,不修改原集合。处理标签、权限、分类等”关系”问题时极其好用。

1-8 成员关系与相等

除了运算,还能判断集合之间的包含关系:

isSubset(of:):自己是不是对方的子集(全包含)。 isSuperset(of:):自己是不是对方的超集(全包住)。 isDisjoint(with:):两家是不是毫无交集。 ==:两家元素是否完全相同。

let house: Set = ["🐶", "🐱"]
let farm: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"]
house.isSubset(of: farm)       // true
farm.isSuperset(of: house)     // true

还有更严格的 isStrictSubset / isStrictSuperset,要求”是子集/超集但不相等”。

1-9 Set 还是 Array

什么时候用 Set?两条经验:一、你不关心顺序时;二、你需要自动去重或频繁判断”有没有”时。比如统计用户点过的标签、去重一份名单、做权限交集,Set 都更自然。

如果你需要”第 N 个”或”保持插入顺序”,那就用数组。Swift 里数组也能去重(转成 Set 再转回),但 Set 的本意就是”无序不重复”,别强行给它排顺序。

Tip

去重一个小技巧:let unique = Array(Set(someArray))。先把数组丢进 Set 去重,再转回数组。代价是顺序会丢失——在意顺序时别这么干。

1-10 选对容器

Set 的定位是”不重复、无顺序”,和数组正好互补。当你只关心”有没有”而非”第几个”时,Set 比数组自然。但它要求元素可哈希——基础类型默认都可哈希,自定义类型需遵循 Hashable 协议(以后讲)。哈希让 Set 的去重和成员查询极快。

insert/remove/contains 处理成员,count/isEmpty 看状态,for-in 遍历但顺序不保证(要顺序用 sorted())。集合运算是 Set 最锋利的能力:union 并集、intersection 交集、subtracting 差集、symmetricDifference 对称差,以及 isSubset/isSuperset/isDisjoint 等关系判断。处理标签、权限、分类等”关系”问题时,这些运算能一行搞定原本要写循环的逻辑。

去重小技巧:let unique = Array(Set(someArray)),把数组丢进 Set 去重再转回。代价是顺序丢失——在意顺序时别这么干。这也点出 Set 的取舍:用顺序换去了重和查询效率。

什么时候用 Set?两条经验:不关心顺序时;需要自动去重或高频判断”有没有”时。比如统计用户点过的标签、去重名单、做权限交集。若需”第 N 个”或保持插入顺序,用数组。Swift 里数组也能转 Set 去重,但 Set 的本意就是无序不重复,别强行给它排顺序。

初学容易把 Set 和数组混用。一个简单的判据:如果你的代码里频繁出现”这个元素在不在里面""把重复的去掉”,那 Set 多半更合适;如果要按下标取、要保持顺序,数组更对路。选对容器,代码又短又对。理解 Set 与数组、字典的分工(有序可重复 / 无序去重 / 键值对),数据结构选型就不慌。三者都类型安全、都可用 let/var 控可变性、都要求元素或键可哈希,这是它们的共性。

1-11 补记:互斥与严格包含

再补一个使用场景:用 isDisjoint(with:) 可以判断两组是否毫无交集,做权限互斥、标签冲突检测时很方便。比如”用户已选的标签”和”互斥标签组”若 disjoint,说明没冲突。isStrictSubset/isStrictSuperset 则要求”是真子集/真超集”(不含相等),当你需要”包含但不止于”的语义时用它们。

Set 的方法不多但每个都锋利。核心记住:去重靠哈希、查成员快、集合运算表达关系。和数组配合(数组做有序存储、Set 做去重判断)是常见组合拳。把 Set 当”关系型工具”而不是”另一种数组”,你才能用出它的精髓。

别为了用 Set 而用 Set。如果你的逻辑强烈依赖顺序或重复,数组才是对的容器。选容器看语义,不看流行。

1-12 小结

Set 存不重复、无顺序的值,元素必须可哈希(基础类型默认都可)。insert/remove/contains 处理成员,count/isEmpty 看状态。遍历顺序不保证,要顺序用 sorted()。集合运算 union/intersection/subtracting/symmetricDifference 与关系判断 isSubset/isSuperset/isDisjoint 是它最锋利的能力。下一章我们看字典——用”键”查”值”的容器。