Kodewick
第 12 课,共 31 课目录

集合

第 12 课,共 31 课 · 第 2 部分 · 容器和循环 · 约 15 分钟

集合是一组没有重复、也没有顺序的值。把各项写在花括号里,像一个没有 : 值 的字典。同一个值放两次,集合只留一个。

main.py
1lit = {3, 1, 4, 1, 5}2print(lit)3print(len(lit))4print(type(lit))
输出
{1, 3, 4, 5}
4
<class 'set'>

这个集合有四项,不是五项:第二个 1 被扔掉了。今晚点亮了哪些灯,就很适合用集合来记:一盏灯要么亮着要么没亮,点两次也不算两盏。

去掉重复

set() 把列表(或者字符串、元组)变成集合,所有重复的都会去掉。想知道「一共有哪几种不同的值」,这是最快的办法。

main.py
1visits = [2, 5, 2, 7, 5, 2]2print(set(visits))3print(len(set(visits)))4ore = ["铁", "煤", "铁", "金", "煤"]5print(sorted(set(ore)))6print(sorted(set("banana")))
输出
{2, 5, 7}
3
['煤', '金', '铁']
['a', 'b', 'n']

想再变回列表,用 sorted(...) 得到排好序的列表,或者用 list(...)。空集合写成 set():单写 {} 是一个空字典。

没有顺序,也没有下标

集合不会按你写的顺序存东西,你看到的顺序也靠不住。如果里面是字符串,每次运行程序,顺序都可能不一样。所以这一课里,字符串的集合都用 sorted() 来显示。

没有顺序,也就没有「第 0 项」:

main.py
1lit = {3, 1, 4}2print(4 in lit)3print(lit[0])
输出
True
Traceback (most recent call last):
  File "main.py", line 3, in <module>
    print(lit[0])
TypeError: 'set' object is not subscriptable

发生了什么: 这种东西不能用 [ ] 取里面的东西。

小技巧: 要顺序、要位置,用列表。只关心「在不在」和「不重复」,用集合。

添加和删除

add 放进一项;放一个已经有的,什么也不会发生。remove 拿掉一项,不在就报 KeyError。discard 也是拿掉一项,不在也不出声。

main.py
1lit = {1, 2}2lit.add(3)3lit.add(2)4print(lit)5lit.remove(1)6print(lit)7lit.discard(9)8print(lit)9lit.remove(9)
输出
{1, 2, 3}
{2, 3}
{2, 3}
Traceback (most recent call last):
  File "main.py", line 9, in <module>
    lit.remove(9)
KeyError: 9

发生了什么: 字典里没有 9 这个键。

update 把一个列表或另一个集合里的每一项都加进来。clear() 把集合清空。

快速的 in

列表也能用 in,但列表得从头一项一项地查。集合能直接找到该去的地方,所以哪怕有一百万项,集合上的 in 也很快。程序要一遍遍地问「这个见过没有」时,就用集合。

main.py
1seen = {"磨坊巷", "池塘路"}2print("池塘路" in seen)3print("山坡街" in seen)4print("山坡街" not in seen)5seen.add("山坡街")6print(len(seen))
输出
True
False
True
3

not in 和 in 正好相反,读起来像一句英语;列表、字符串、字典上也都能用。

并集、交集、差集

集合可以像图里的几个圆圈那样组合。每种运算都造出一个新集合,原来的两个不变。

main.py
1mon = {1, 2, 3, 4}2tue = {3, 4, 5}3print(mon | tue)4print(mon & tue)5print(mon - tue)6print(tue - mon)7print(mon ^ tue)
输出
{1, 2, 3, 4, 5}
{3, 4}
{1, 2}
{5}
{1, 2, 5}
写法方法得到在这里是
a | ba.union(b)在 a 里或在 b 里(或两边都在)两天里任一天亮过的灯
a & ba.intersection(b)只要两边都有的两天都亮过的灯
a - ba.difference(b)在 a 里、不在 b 里只有周一亮过的灯
a ^ ba.symmetric_difference(b)只在一边的只有其中一天亮过的灯
先猜一猜

这段会显示什么?

1a = {1, 2, 3}2b = {2, 3, 4}3print(len(a | b), len(a & b))

方法和符号的作用一样。有一点不同:方法还能接受列表,所以 a.union([7, 8]) 可以,a | [7, 8] 会报错。

子集

a <= b 问的是「a 的每一项是不是都在 b 里」,和 a.issubset(b) 一样。== 在两个集合的项完全相同时为 True,不管你写的顺序是什么。

main.py
1needed = {"灯", "油"}2bag = {"油", "地图", "灯"}3print(needed <= bag)4print(needed.issubset({"油"}))5print({1, 2, 3} == {3, 2, 1})6print(sorted(bag - needed))
输出
True
False
True
['地图']

方法一览

写法作用
set(x)由 x 里的项组成的集合,去掉重复
s.add(x)加上 x
s.update(x)加上 x 里的每一项
s.remove(x)拿掉 x(没有就报 KeyError)
s.discard(x)拿掉 x,有的话
s.clear()全部拿掉
x in sx 在不在集合里(很快)
a | b、a & b、a - b、a ^ b并集、交集、差集、对称差
a <= b / a.issubset(b)a 的每一项是不是都在 b 里

练一练

写好代码,点检查。提示随便看,实在想不出来再看答案。

练习 1

有几户

显示列表里有几个不同的门牌号。

要显示
4
exercise1.py
1visits = [12, 4, 12, 7, 4, 12, 9]2# 在这里写
输出
点「运行」或按 Ctrl+Enter
练习 2

还黑着的灯

all_lamps 是镇上所有的灯,lit 是 Wick 已经点亮的。把还黑着的灯排好序,作为列表显示出来。

要显示
[1, 3, 4]
exercise2.py
1all_lamps = {1, 2, 3, 4, 5, 6}2lit = {2, 5, 6}3# 在这里写
输出
点「运行」或按 Ctrl+Enter
练习 3

两座矿都有的

把两座矿里都挖到过的矿石种类,排好序,作为列表显示出来。

要显示
['煤', '铁']
exercise3.py
1north = ["铁", "煤", "金", "铁"]2south = ["煤", "铜", "铁"]3# 在这里写
输出
点「运行」或按 Ctrl+Enter

这一课学会了

  • 集合里的值不重复、没有顺序:{1, 2, 3}。空集合是 set()。
  • set(列表) 去掉重复;sorted(set(...)) 把它们变回排好序的列表。
  • 没有顺序就没有下标:s[0] 会报错。
  • add、update、remove、discard 改集合;集合上的 in 很快。
  • |、&、-、^ 组合集合;<= 判断子集。