4.4 集合 set
数据结构的最后一位成员。先看一个具体需求:一个抽奖名单里有人重复提交了,怎么去掉重复的名字?
用列表 + 循环当然能写出来,但 Python 有个数据结构天生就干这个——集合(set)。它只有两条脾气:
- 不重复:同样的元素只保留一个
- 无顺序:元素没有位置编号,不能问"第几个"
这一节内容不多,重点掌握"去重"和"交并差"两个绝活。
创建集合
用花括号 {} 直接列元素(注意和字典区分:字典里是 键: 值 对,集合里是单个值):
tags = {"红", "绿", "蓝"}
print(type(tags)) # <class 'set'>
它的两条脾气眼见为实。放进重复元素,自动只留一个:
nums = {1, 2, 2, 3, 3, 3}
print(nums) # {1, 2, 3}
没有顺序,打印出来的排列可能和你写的不一样,也不能索引:
tags = {"红", "绿", "蓝"}
print(tags[0]) # ❌ TypeError: 'set' object is not subscriptable
想逐个处理就用 for 遍历(顺序不保证):
for t in tags:
print(t)
⚠️ 特例:空集合必须写
set(),不能写{}! 因为空花括号被字典抢注了:a = {}print(type(a)) # <class 'dict'> ← 是空字典!b = set()print(type(b)) # <class 'set'> ← 这才是空集合
绝活一:一行代码去重
开头的抽奖问题,答案就一行——列表转集合(去重),再转回列表:
names = ["小明", "小红", "小明", "小刚", "小红"]
unique = list(set(names))
print(unique) # ['小刚', '小明', '小红'](顺序可能变化)
拆开看这个嵌套(和 int(input()) 一样由内到外):
set(names):列表 → 集合,重复元素在这一步被自动扔掉list(...):集合 → 列表,变回可以索引、排序的列表
list(set(某列表)) 值得当口诀背下来。注意副作用:去重后原来的顺序会乱(集合无序)。乱了想恢复整齐可以接一个排序:sorted(set(nums))。
基本操作:增、删、查
tags = {"红", "绿"}
tags.add("蓝") # 增:加一个元素(重复添加不报错,但也不会多出一份)
tags.remove("红") # 删:元素不存在会报 KeyError
tags.discard("紫") # 删的温柔版:不存在也不吭声
print(len(tags)) # 2
print("绿" in tags) # True
remove 和 discard 的选择逻辑,与字典的 [] 和 get() 如出一辙:该在而不在算 bug → 用 remove 让它报错;可有可无 → 用 discard。
in 判断要特别说一句:集合做 in 判断的速度极快,而且不随元素变多而变慢;列表则是从头到尾一个个找,数据一多就慢。所以"频繁判断某元素在不在一大堆数据里"的场景,先把列表转成集合是行业常规操作。现阶段数据量小感受不到差距,先把结论记住。
绝活二:集合运算——交集、并集、差集
两个集合之间可以做数学课上的"集合运算",三个运算符:
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a & b) # {3, 4} 交集:两边都有的
print(a | b) # {1, 2, 3, 4, 5, 6} 并集:合在一起(自动去重)
print(a - b) # {1, 2} 差集:a 有而 b 没有的
用文氏图理解:
a b
┌──────┬──┬──────┐
│ 1 2 │3 │ 5 6 │
│ │4 │ │
└──────┴──┴──────┘
↑
a & b 就是中间重叠区
a | b 是整个图形
a - b 是左边不重叠的部分(注意 a - b ≠ b - a)
换成人话场景立刻就懂了:
python_class = {"小明", "小红", "小刚"}
english_class = {"小红", "小丽"}
print(python_class & english_class) # {'小红'} 两门都报的
print(python_class | english_class) # 四个人 至少报了一门的
print(python_class - english_class) # {'小明', '小刚'} 只报了 Python 的
print(english_class - python_class) # {'小丽'} 只报了英语的
这类"找共同好友"、"找两份名单的差异"的需求,用列表写要好几层循环,用集合就是一个运算符。
全章收官:四种数据结构怎么选
四兄弟到齐,一张表看清各自的定位:
| 结构 | 写法 | 有序? | 可改? | 可重复? | 一句话定位 |
|---|---|---|---|---|---|
| 列表 list | [1, 2] | ✅ | ✅ | ✅ | 一串数据,默认首选 |
| 元组 tuple | (1, 2) | ✅ | ❌ | ✅ | 定死不变的固定搭配 |
| 字典 dict | {"k": v} | — | ✅ | 键不可 | 有名字的数据,按键查值 |
| 集合 set | {1, 2} | ❌ | ✅ | ❌ | 去重、判存在、交并差 |
选择思路走一遍流程:
- 数据是"名字 → 值"的对应关系?→ 字典
- 需要去重或交并差运算?→ 集合
- 内容永远不变?→ 元组
- 其他情况 → 列表
练习
新建 practice44.py 完成:
1. 把列表 ["a", "b", "a", "c", "b"] 去重后从小到大排序打印。
2. 你的爱好 {"篮球", "音乐", "编程"},朋友的爱好 {"音乐", "游戏"}。求:共同爱好、两人爱好的总和、只有你有的爱好。
3. 不运行,先猜输出,再验证:
s = {1, 1, 2, 3}
print(len(s))
e = {}
print(type(e))
a = {1, 2, 3}
b = {2, 3, 4}
print(a - b == b - a)
4. 下面代码想统计列表里有多少个不同的数字,但结果不对,找出原因修好它:
nums = [3, 7, 3, 1, 7, 7]
count = len(nums)
print(f"共有 {count} 个不同的数字")
点击查看答案
# 1
items = ["a", "b", "a", "c", "b"]
print(sorted(set(items))) # ['a', 'b', 'c']
# 2
mine = {"篮球", "音乐", "编程"}
friend = {"音乐", "游戏"}
print("共同爱好:", mine & friend) # {'音乐'}
print("所有爱好:", mine | friend)
print("只有我有:", mine - friend) # {'篮球', '编程'}
第 3 题:
len(s)→3(重复的 1 只留一个:{1, 2, 3})type(e)→<class 'dict'>({}是空字典,不是空集合!)a - b == b - a→False(a - b是 {1},b - a是 {4},差集有方向)
第 4 题: len(nums) 数的是全部元素(6 个),没有去重。先转集合再数:
nums = [3, 7, 3, 1, 7, 7]
count = len(set(nums))
print(f"共有 {count} 个不同的数字") # 共有 3 个不同的数字
本章小结
- 集合两条脾气:不重复、无顺序(没有索引,不能
s[0]) - 空集合必须
set(),{}是空字典 - 去重口诀:
list(set(lst));要整齐就sorted(set(lst)) add增,remove(严格)/discard(温柔)删;集合的in判断飞快&交集、|并集、-差集(有方向)- 选型流程:键值对→字典,去重/交并差→集合,定死→元组,其余→列表
数据结构四兄弟集齐!它们是后面一切程序的原材料。下一章学习编程中最重要的概念之一:5.1 函数入门