Python 集合

Python 集合

在 Python 的数据结构世界里,如果说列表(List)是全能战士,字典(Dict)是键值对大师,那么集合(Set)就是那个追求"唯一"与"极速"的效率专家。它源于数学中的集合论,以其独特的无序性元素唯一性,在数据去重、成员资格检查以及集合运算等场景中展现出无与伦比的优势。


集合是什么?核心特性一览

Python 中的集合(Set)是一个无序的、可变的、元素唯一的容器。

  • 无序性 (Unordered) : 集合中的元素没有固定的顺序,这意味着你不能通过索引(如 my_set[0])来访问元素。每次打印集合,元素的显示顺序都可能不同。
  • 元素唯一性 (Unique Elements) : 集合中不允许存在重复的元素。当你尝试添加一个已存在的元素时,集合不会发生任何变化。这个特性使其成为数据去重的绝佳工具。
  • 可变性 (Mutable) : 集合本身是可变的,你可以随时添加或删除元素。但请注意,集合中的元素本身必须是不可变类型(即可哈希的),例如数字、字符串、元组。列表或字典这类可变对象不能作为集合的元素。

集合与其他数据结构对比

为了更清晰地理解集合的定位,我们将其与列表、元组和字典进行对比。

特性 列表 (List) 元组 (Tuple) 字典 (Dict) 集合 (Set)
有序性 有序 有序 3.7+ 插入有序 无序
可变性 可变 不可变 可变 可变
元素唯一性 允许重复 允许重复 键唯一,值可重复 元素唯一
索引访问 支持 支持 通过键访问 不支持
核心用途 存储有序序列 存储常量序列 存储键值对映射 去重、快速查找、集合运算

️集合的创建与初始化

创建集合主要有两种方式,但有一个新手极易踩的"坑"。

  1. 使用花括号 {} 这是最直观的方式,但不能用于创建空集合

    ini 复制代码
    # 创建包含元素的集合
    fruits = {'apple', 'banana', 'orange'}
    print(fruits)  # 输出: {'banana', 'apple', 'orange'} (顺序可能不同)
    
    # 创建时自动去重
    numbers = {1, 2, 3, 2, 1}
    print(numbers)  # 输出: {1, 2, 3}
  2. 使用 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) ,而不是空集合。

bash 复制代码
not_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() 是更安全的选择。

ini 复制代码
my_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() 函数。

补充知识

  1. 集合推导式 (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}
  2. 不可变集合 (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])  # 输出: 这是一个不可变集合
  3. 性能优势:为什么集合查找这么快? 集合的底层是基于哈希表 (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}秒")  # 几乎是瞬间完成
相关推荐
时光足迹2 小时前
Tiptap之标注组件
前端·javascript·react.js
2401_867623982 小时前
解决Navicat多图纸模型工作区协同报错怎么办_外键关联与语法解析
jvm·数据库·python
时光足迹3 小时前
Tiptap 之自定义脚注组件
前端·javascript·react.js
时光足迹3 小时前
Tiptap之造字组件
前端·javascript·react.js
jump_jump3 小时前
用官方模板理解 Decky 插件:一次从模板到架构的速览
javascript·python·游戏
张元清3 小时前
React 表单处理:防抖校验、自动保存草稿与受控输入
前端·javascript·面试
sinat_383437363 小时前
如何为表名加上图标前缀_根据表前缀自动匹配图标
jvm·数据库·python
Lee川3 小时前
React 首页秒开优化:用 KeepAlive 实现丝滑的页面缓存
前端·react.js
Hilaku3 小时前
给技术团队定规范,为什么 90% 最后都变成了走形式?
前端·javascript·程序员