Python 集合
报名名单中可能重复出现同一个姓名。现在有一个问题:如何只保留不重复的姓名,同时接受集合不保证原顺序这一特点?
案例:整理报名名单
需求:将重复的报名姓名整理为不重复的数据。
name_list = ["小明", "小红", "小明", "小刚", "小红"]
name_set = set(name_list)
print(name_set)重复的 "小明" 和 "小红" 只会保留一份。集合不保证元素的位置顺序,因此实际输出顺序可能和原列表不同。
集合和数学中的集合很像,主要解决两个问题:
- 数据去重
- 计算两组数据的交集、并集、差集
集合创建与转换
非空集合可以使用 {} 创建,元素之间用逗号分隔:
course_set = {"Python", "MySQL", "Linux"}
print(course_set)也可以使用 set() 把列表、元组或字符串转换成集合:
number_set = set([1, 2, 2, 3, 3, 3])
char_set = set("hello")
print(number_set)
print(char_set)集合的显示顺序可能与写入顺序不同,但重复元素一定会被去掉。
空集合创建
空集合必须使用 set() 创建:
empty_set = set()
empty_dict = {}
print(type(empty_set))
print(type(empty_dict))运行结果:
<class 'set'>
<class 'dict'>{} 表示空字典,不是空集合。这是刚开始最容易写错的地方。
集合特性
集合的使用边界可以概括为三个特点:
- 元素不重复
- 不保证元素的位置顺序
- 集合本身可以修改
元素唯一性
创建集合时,重复元素会自动合并:
number_set = {1, 2, 2, 3, 3, 3}
print(number_set)集合适合判断“某个值是否出现过”,但不记录同一个值出现了多少次。
无索引访问
集合不保证元素的位置顺序,因此不能像列表、元组那样按索引取值或切片。
如果需求要求按位置读取,就不能直接使用集合:
course_set = {"Python", "MySQL", "Linux"}
print(course_set[0])运行后会抛出异常:
TypeError: 'set' object is not subscriptable元素类型限制
集合需要判断元素是否已经存在,因此放入集合的元素必须可哈希。初学阶段可以先记住:数字、字符串和只包含不可变数据的元组可以作为集合元素;列表、字典和集合本身不能直接放进集合。
position_set = {(10, 20), (30, 40)}
print(position_set)如果把列表放进集合,会抛出 TypeError: unhashable type: 'list'。
集合修改
报名课程会增加或取消,集合可以修改,但不能按位置操作元素。
元素添加
add() 添加一个元素,update() 批量添加多个元素:
course_set = {"Python", "MySQL"}
course_set.add("Linux")
course_set.update(["Redis", "Docker"])
print(course_set)如果添加的元素已经存在,集合不会重复保存,也不会报错。
元素删除
集合常用的删除操作如下:
| 操作 | 作用 | 元素不存在时 |
|---|---|---|
remove(data) | 删除指定元素 | 抛出 KeyError |
discard(data) | 删除指定元素 | 不报错 |
pop() | 删除并返回一个元素 | 空集合会抛出 KeyError |
clear() | 清空集合 | 不报错 |
course_set = {"Python", "MySQL", "Linux"}
course_set.remove("MySQL")
course_set.discard("Redis")
print(course_set)这里的 "Redis" 不在集合中,但 discard() 不会报错。如果换成 remove("Redis"),程序会抛出 KeyError。
警告
集合不保证元素的位置顺序,因此 pop() 删除的是其中一个元素,不能把它理解成“删除最后一个元素”。
成员判断与遍历
如果只关心某门课程是否报名,使用 in 和 not in 判断元素是否存在:
course_set = {"Python", "MySQL", "Linux"}
print("Python" in course_set)
print("Redis" not in course_set)运行结果:
True
True使用 for 可以遍历集合,但不能依赖它的遍历顺序:
course_set = {"Python", "MySQL", "Linux"}
for course in course_set:
print(course)如果业务需要按固定顺序处理数据,就不应该只使用集合保存这些数据。
集合运算
两组学员选择了不同的课程时,新的问题是:如何找出共同课程、全部课程,以及只被一组学员选择的课程?
group_a = {"Python", "MySQL", "Linux"}
group_b = {"Python", "Redis", "Linux"}集合可以直接完成四种运算:
| 运算 | 符号 | 含义 |
|---|---|---|
| 交集 | & | 两边都有的元素 |
| 并集 | | | 合并两边的元素并去重 |
| 差集 | - | 左边有、右边没有的元素 |
| 对称差集 | ^ | 只在其中一边出现的元素 |
group_a = {"Python", "MySQL", "Linux"}
group_b = {"Python", "Redis", "Linux"}
print("交集:", group_a & group_b)
print("并集:", group_a | group_b)
print("差集:", group_a - group_b)
print("对称差集:", group_a ^ group_b)结果中的元素顺序可能不同,但元素内容如下:
- 交集:
"Python"、"Linux" - 并集:
"Python"、"MySQL"、"Redis"、"Linux" - 差集:
"MySQL" - 对称差集:
"MySQL"、"Redis"
差集有方向。group_a - group_b 和 group_b - group_a 的结果通常不同。
案例:比较两个字符串
需求:比较两个字符串中出现的字符,分别找出共有字符、不同字符和 text_a 多出的字符。
- 找出两个字符串共有的字符
- 找出只在其中一个字符串出现的字符
- 找出
text_a比text_b多出的字符
text_a = "hello world!"
text_b = "hello python!"先把两个字符串转换成集合,再做集合运算。转换后只记录字符是否出现,不再记录每个字符出现的次数。
参考答案
text_a = "hello world!"
text_b = "hello python!"
char_set_a = set(text_a)
char_set_b = set(text_b)
# 两个字符串共有的字符
print("共有字符:", char_set_a & char_set_b)
# 只在其中一个字符串出现的字符
print("不同字符:", char_set_a ^ char_set_b)
# text_a 有、text_b 没有的字符
print("text_a 多出的字符:", char_set_a - char_set_b)集合结果中可能包含空格字符。观察结果时,注意不要漏掉显示为 ' ' 的空格。
常见问题
去重后的顺序变化
下面的代码可以去重,但不能保证保留列表原来的顺序:
number_list = [3, 1, 2, 3, 2]
number_list = list(set(number_list))
print(number_list)如果任务要求“去重后仍保持原顺序”,还需要使用其他写法,后面的综合练习会继续处理。
空集合与空字典
{} 创建的是字典。需要空集合时,必须写成 set()。
元素顺序依赖
不要依赖集合的显示顺序,也不要尝试使用索引。需要按位置保存和处理数据时,应使用列表或元组。
小结
本节先回答五个问题:
- 什么时候使用集合?需要去重、判断成员或比较多组数据时
- 集合有哪些边界?元素不重复、不保证顺序,不能使用索引和切片
- 空集合如何创建?必须使用
set(),{}表示空字典 - 如何维护集合?
add()添加一个元素,update()批量添加,删除时根据是否允许缺失选择remove()或discard() - 如何比较两组数据?使用
&、|、-、^分别计算交集、并集、差集和对称差集
练习
- 把包含重复数字的列表转换成集合,观察结果
- 使用
add()、update()、remove()和discard()修改集合 - 定义两个集合,分别计算交集、并集、差集和对称差集
- 用集合找出
"hello world"和"hello python"共有的字符