总结Python的set集合操作

Last Updated: 2023-12-22 08:56:34 Friday

-- TOC --

Python的set容器是无序不重复的元素集合,set对象的基本使用场景是:

快速的membership testing

$ python -m timeit -s 'a=(1,2,3,4,5,6,7,8)' '8 in a'
5000000 loops, best of 5: 54.5 nsec per loop
$ python -m timeit -s 'a=set((1,2,3,4,5,6,7,8))' '8 in a'
20000000 loops, best of 5: 17 nsec per loop

集合数学基础

创建set

创建set对象,可以使用set([iterable]),或者大括号{enum},使用大括号时,枚举集合元素,否则就是在创建dict对象(语法冲突)。

>>> a = set()  # empty set
>>> a
set()
>>> b = set('abcde')  # iterable
>>> b
{'e', 'd', 'a', 'b', 'c'}
>>> c = {'abcde'}  # enum
>>> c
{'abcde'}
>>> d = {1,2,3,4,5,6}  # enum
>>> d
{1, 2, 3, 4, 5, 6}
>>> len(a)
0
>>> len(b)
5
>>> len(c)
1
>>> len(d)
6
>>> f = set([1,2,3,4,5])  # iterable
>>> f
{1, 2, 3, 4, 5}

我们可以通过add()接口向set中添加元素,但是有个细节需要注意,在Python中,1 == 1.0 为True,因此:

>>> a = set()
>>> a.add(1)
>>> a.add(1.0)
>>> a
{1}
>>> a.add(1.000000)
>>> a
{1}
>>> 1 == 1.0
True

membership testing使用经典的in关键词:

>>> 'a' in b
True
>>> 'gg' in b
False

set对象属于mutable,我们可以用add成员函数来增加set对象中的元素,不过如果增加重复元素,操作就无效了,因此我们常常使用set的这个特性来做去重。set对象最特别的应用,就是集合计算,下面这段是来自python官方的示例:

>>> a = set('abracadabra')
>>> b = set('alacazam')
>>> a                                  # unique letters in a
{'a', 'r', 'b', 'c', 'd'}
>>> a - b                              # letters in a but not in b
{'r', 'd', 'b'}
>>> a | b                              # letters in a or b or both
{'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'}
>>> a & b                              # letters in both a and b
{'a', 'c'}
>>> a ^ b                              # letters in a or b but not both
{'r', 'd', 'b', 'm', 'z', 'l'}

set对象是一个集合体,无序,我们增加一个元素后,并不能确定这个元素在遍历的时候,出现在什么时候。set对象的实现,肯定对内部元素有一种排序方式,不过对于使用set对象而言,这种内部的排序方式不需要知道,也是不可靠的依据,我们只需要知道无序即可。

元素去重

set对象用来去重(去掉重复元素)是常用的操作,只需一行代码即可搞定:

>>> a = [1,1,2,2,3,3,4,4,5,5]
>>> list(set(a))
[1, 2, 3, 4, 5]
>>> b = (1,1,2,2,3,3,4,4,5,5)
>>> tuple(set(b))
(1, 2, 3, 4, 5)

在使用setcomp时,依然有去重的效果:

>>> {a for a in (1,2,2,3,3,4,4)}
{1, 2, 3, 4}

并集

使用set对象的union函数,或者 | 操作符,实现并集操作:

>>> a
{1, 2, 3}
>>> b
{3, 4, 5}
>>> a.union(b)
{1, 2, 3, 4, 5}
>>> b.union(a)
{1, 2, 3, 4, 5}
>>> a
{1, 2, 3}
>>> b
{3, 4, 5}
>>> a | b
{1, 2, 3, 4, 5}

用 + 号是不行的。

用union或 | 合并,会生成一个新的set对象,因此需要用一个额外的变量来指向合并后的结果。也可以使用update,直接原地修改(in-place):

>>> a
{1, 2, 3}
>>> b
{3, 4, 5}
>>> a.update(b)
>>> a
{1, 2, 3, 4, 5}
>>> b
{3, 4, 5}
>>>
>>> b.update(a)
>>> b
{1, 2, 3, 4, 5}
>>> a
{1, 2, 3}
>>> c
{4}
>>> id(a)
41125032
>>> a |= c
>>> id(a)
41125032

update和 |= 操作一样,原地更新!

交集

使用intersection函数,或者 & 操作符:

>>> a = {1,2,3}
>>> b = {3,4,5}
>>> c = a.intersection(b)
>>> c
{3}
>>> a
{1, 2, 3}
>>> b
{3, 4, 5}
>>> c = a & b
>>> c
{3}

注意intersection_update函数与update的异同,都是in-place modification,前者是交,后者是并:

>>> a = {1,2,3}
>>> a.intersection({3,4,5})
{3}
>>> a
{1, 2, 3}
>>> a.intersection_update({3,4,5})
>>> a
{3}
>>>
>>> b = {3,4,5}
>>> b.update({1,2,3})
>>> b
{1, 2, 3, 4, 5}

集合的差

有两个set,a和b,判断元素在a中而不在b中的方法,可以直接用 - 操作符,或者difference函数:

>>> a
{1, 2, 3}
>>> b
{3, 4, 5}
>>> a-b
{1, 2}
>>> b-a
{4, 5}
>>> a.difference(b)
{1, 2}
>>> b.difference(a)
{4, 5}

还有 difference_update 函数:

>>> a = {1,2,3}
>>> a.difference({3,4,5})
{1, 2}
>>> a.difference_update({3,4,5})
>>> a
{1, 2}

判断两个集合是否相交

用两个set交际是否为空可以判断:

>>> a
{1, 2}
>>> b
{3, 4, 5}
>>> a & b == set()  # set() creates an empty set
True

更优雅的方法,使用isdisjoint函数:

>>> a
{1, 2, 3}
>>> b
{3, 4, 5}
>>> a.isdisjoint(b)
False
>>> b.isdisjoint(a)
False
>>> a = {1,2}
>>> a.isdisjoint(b)
True
>>> b.isdisjoint(a)
True

子集

用交集是否与子集相同,可以判断是否为某个集合的子集:

>>> b
{3, 4, 5}
>>> c
{3}
>>> c & b == c
True

更优雅的方式,是使用issubset函数:

>>> b
{3, 4, 5}
>>> c
{3}
>>> c.issubset(b)
True
>>> b.issubset(c)
False

超集

可以用合并后是否与自己相同来判断是否为对方的超集:

>>> c
{3}
>>> b
{3, 4, 5}
>>> b | c == b
True

更优雅的方法,是用issuperset函数:

>>> b
{3, 4, 5}
>>> c
{3}
>>> b.issuperset(c)
True
>>> b.issuperset(b)
True

对称差

有的时候,我们需要判断,哪些个元素只出现在一个集合中。

>>> a
{1, 2, 3}
>>> b
{3, 4, 5}
>>> (a|b) - (a&b)
{1, 2, 4, 5}
>>> a ^ b
{1, 2, 4, 5}

set对象有个成员函数,symmetric_difference,干同样的事情,我就是觉得这个函数名太长了:

>>> a = {1,2,3}
>>> b = {3,4,5}
>>> a.symmetric_difference(b)
{1, 2, 4, 5}
>>> b.symmetric_difference(a)
{1, 2, 4, 5}

还有个symmetric_difference_update函数,用对称差原地更新set对象:

>>> a = {1,2,3}
>>> a.symmetric_difference_update({3,4,5})
>>> a
{1, 2, 4, 5}
>>>
>>> a = {1,2,3}
>>> a.update({3,4,5})
>>> a
{1, 2, 3, 4, 5}

in-place methods:update,difference_update,intersection_update,symmetric_difference_update

frozenset

Python内置了两种集合对象,set和frozenset,set是mutable,对应的frozenset就是immutable。(有点类似list和tuple的关系)

>>> b = frozenset()
>>> b.add(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'frozenset' object has no attribute 'add'
>>>
>>> c = frozenset((1,2,3,4,5))
>>> c
frozenset({1, 2, 3, 4, 5})
>>> [x for x in dir(c) if not x.startswith('__')]
['copy', 'difference', 'intersection', 'isdisjoint', 'issubset', 'issuperset', 'symmetric_difference', 'union']

从上面的code snippet可以看到,frozenset对象没有add操作,因为其被定义为immutable,在创建的时候,就需要确定所有的成员元素。同时,它的成员函数比较少,都是用来进行集合比较操作的一类。所有的update类都不见了!

本文链接:https://cs.pynote.net/sf/python/202205051/

-- EOF --

-- MORE --