Python 集合
在 Python 的数据结构世界里,如果说列表(List)是全能战士,字典(Dict)是键值对大师,那么集合(Set)就是那个追求"唯一"与"极速"的效率专家。它源于数学中的集合论,以其独特的无序性 和元素唯一性,在数据去重、成员资格检查以及集合运算等场景中展现出无与伦比的优势。
集合是什么?核心特性一览
Python 中的集合(Set)是一个无序的、可变的、元素唯一的容器。
- 无序性 (Unordered) : 集合中的元素没有固定的顺序,这意味着你不能通过索引(如
my_set[0])来访问元素。每次打印集合,元素的显示顺序都可能不同。 - 元素唯一性 (Unique Elements) : 集合中不允许存在重复的元素。当你尝试添加一个已存在的元素时,集合不会发生任何变化。这个特性使其成为数据去重的绝佳工具。
- 可变性 (Mutable) : 集合本身是可变的,你可以随时添加或删除元素。但请注意,集合中的元素本身必须是不可变类型(即可哈希的),例如数字、字符串、元组。列表或字典这类可变对象不能作为集合的元素。
集合与其他数据结构对比
为了更清晰地理解集合的定位,我们将其与列表、元组和字典进行对比。
| 特性 | 列表 (List) | 元组 (Tuple) | 字典 (Dict) | 集合 (Set) |
|---|---|---|---|---|
| 有序性 | 有序 | 有序 | 3.7+ 插入有序 | 无序 |
| 可变性 | 可变 | 不可变 | 可变 | 可变 |
| 元素唯一性 | 允许重复 | 允许重复 | 键唯一,值可重复 | 元素唯一 |
| 索引访问 | 支持 | 支持 | 通过键访问 | 不支持 |
| 核心用途 | 存储有序序列 | 存储常量序列 | 存储键值对映射 | 去重、快速查找、集合运算 |
️集合的创建与初始化
创建集合主要有两种方式,但有一个新手极易踩的"坑"。
-
使用花括号
{}这是最直观的方式,但不能用于创建空集合。ini# 创建包含元素的集合 fruits = {'apple', 'banana', 'orange'} print(fruits) # 输出: {'banana', 'apple', 'orange'} (顺序可能不同) # 创建时自动去重 numbers = {1, 2, 3, 2, 1} print(numbers) # 输出: {1, 2, 3} -
使用
set()构造函数 这是创建空集合的唯一正确方式,也可以从其他可迭代对象(如列表、元组)创建集合。ini# 创建空集合 empty_set = set() print(type(empty_set)) # 输出: <class 'set'> # 从列表创建集合并去重 my_list = [1, 2, 3, 2, 1] my_set = set(my_list) print(my_set) # 输出: {1, 2, 3}
常见错误 #1:用
{}创建空集合千万不要这样做!
{}创建的是一个空字典 (dictionary) ,而不是空集合。
bashnot_a_set = {} print(type(not_a_set)) # 输出: <class 'dict'>
集合的增删改查操作
集合的操作非常直观,但删除操作中有个细节需要注意。
| 操作类型 | 方法/运算符 | 描述 | 代码示例 |
|---|---|---|---|
| 增加 | add(element) |
添加单个元素 | my_set.add(4) |
update(iterable) |
添加多个元素(来自列表、元组等) | my_set.update([5, 6]) |
|
| 删除 | remove(element) |
删除指定元素,若元素不存在则抛出 KeyError |
my_set.remove(3) |
discard(element) |
删除指定元素,若元素不存在则什么也不做(推荐使用) | my_set.discard(3) |
|
pop() |
随机删除并返回一个元素(因为集合无序) | item = my_set.pop() |
|
clear() |
清空集合中的所有元素 | my_set.clear() |
|
| 查询 | in / not in |
检查元素是否存在,速度极快 (O(1)) | if 3 in my_set: |
len(set) |
获取集合中元素的数量 | len(my_set) |
常见错误 #2:混淆
remove()和discard()当你不确定一个元素是否存在于集合中时,使用
remove()可能会导致程序因KeyError而崩溃。在这种情况下,使用discard()是更安全的选择。
inimy_set = {1, 2, 3} my_set.remove(4) # 报错: KeyError: 4 my_set.discard(4) # 不会报错,安全执行
集合没有直接的"修改"操作。如果你想"修改"一个元素,实际上是先删除旧元素,再添加新元素。
集合的数学运算
集合最强大的功能之一就是支持数学中的集合运算,这让处理两组数据之间的关系变得异常简单。
假设有两个集合:
ini
A = {1, 2, 3, 4, 5}
B = {4, 5, 6, 7, 8}
| 运算名称 | 运算符 | 方法 | 描述 | 结果示例 |
|----------|---------|-----------------------------|---------------------|----------------------|----------------------------|
| 并集 | `A | B` | A.union(B) | 包含两个集合中所有不重复的元素 | {1, 2, 3, 4, 5, 6, 7, 8} |
| 交集 | A & B | A.intersection(B) | 包含两个集合共有的元素 | {4, 5} |
| 差集 | A - B | A.difference(B) | 包含在A中但不在B中的元素 | {1, 2, 3} |
| 对称差集 | A ^ B | A.symmetric_difference(B) | 包含在A或B中,但不同时在两者中的元素 | {1, 2, 3, 6, 7, 8} |
此外,还有一些用于判断集合关系的方法:
A.issubset(B): 判断A是否为B的子集。A.issuperset(B): 判断A是否为B的超集。A.isdisjoint(B): 判断A和B是否没有交集(即交集为空)。
与其他数据结构的转换
集合与其他数据结构的转换非常频繁,尤其是在去重场景下。
ini
# 1. 列表/元组 转 集合 (主要用途:去重)
my_list = [1, 2, 2, 3, 4, 4]
unique_set = set(my_list)
print(unique_set) # 输出: {1, 2, 3, 4}
# 2. 集合 转 列表/元组
# 注意:转换后的顺序是不确定的!
my_set = {3, 1, 4, 1, 5}
list_from_set = list(my_set)
print(list_from_set) # 输出可能是 [1, 3, 4, 5],也可能是其他顺序
# 如果需要排序,可以结合 sorted() 函数
sorted_list = sorted(my_set)
print(sorted_list) # 输出: [1, 3, 4, 5]
# 3. 保持顺序的去重技巧
# 如果想对列表去重并保持原有顺序,可以使用 dict.fromkeys()
original_list = [3, 1, 4, 1, 5, 9, 2, 6, 5]
unique_ordered_list = list(dict.fromkeys(original_list))
print(unique_ordered_list) # 输出: [3, 1, 4, 5, 9, 2, 6]
常见错误 #3:期望集合转换后保持顺序
由于集合是无序的,当你把它转换成列表时,元素的顺序是无法保证的。如果你需要一个有序且无重复的列表,记得在转换后使用
sorted()函数。
补充知识
-
集合推导式 (Set Comprehension) 和列表推导式类似,集合推导式提供了一种简洁的方式来创建集合。
ini# 创建一个包含0-9平方的集合 squares = {x**2 for x in range(10)} print(squares) # 输出: {0, 1, 4, 9, 16, 25, 36, 49, 64, 81} # 带条件的集合推导式 even_squares = {x**2 for x in range(10) if x % 2 == 0} print(even_squares) # 输出: {0, 4, 16, 36, 64} -
不可变集合 (frozenset) 标准的
set是可变的,因此它本身是不可哈希的,不能作为另一个集合的元素或字典的键。frozenset就是为了解决这个问题而生的。它一旦创建,内容就无法更改。ini# 创建 frozenset fs = frozenset([1, 2, 3]) # fs.add(4) # 报错: AttributeError,不可变 # 作为另一个集合的元素 set_of_sets = {fs, frozenset([4, 5, 6])} print(set_of_sets) # 作为字典的键 my_dict = {fs: "这是一个不可变集合"} print(my_dict[fs]) # 输出: 这是一个不可变集合 -
性能优势:为什么集合查找这么快? 集合的底层是基于哈希表 (Hash Table) 实现的。这意味着检查一个元素是否在集合中(
x in my_set)的时间复杂度接近 O(1),无论集合有多大,查找速度都几乎不变。相比之下,在列表中查找元素的时间复杂度是 O(n),需要逐个遍历,数据量大时速度会慢得多。python# 性能对比示例 import time large_list = list(range(1000000)) large_set = set(large_list) # 列表查找 (慢) start = time.time() 999999 in large_list print(f"列表查找耗时: {time.time() - start:.5f}秒") # 耗时较长 # 集合查找 (快) start = time.time() 999999 in large_set print(f"集合查找耗时: {time.time() - start:.5f}秒") # 几乎是瞬间完成