万物之中, 希望至美.

「Python3学习笔记」读书笔记—集合

2018.06.29

集合存储的是非重复对象,所谓的非重复对象是指:除了不是同一对象外,值也不能相同。

# Python集合判重公式
(a is b) OR (hash(a) == hash(b) AND a == b)

如果不是同一对象,那么先判断哈希值,然后比较内容。因为受限于哈希算法,不同内容可能返回相同的哈希值(哈希碰撞),那么就有必要继续比较内容是否相同。

那么为什么要先比较哈希值,而不直接比较内容呢?首先,与大多数内容(例如字符串)相比,整数哈希值比较的性能高得多;其次,哈希值不同,内容肯定不同,这时就没必要再继续比较内容了。

按操作方式来分的话,集合可分为可变(set)和不可变(frozenset)两个版本,内部实现完全相同。集合使用数组实现的哈希表来存储元素对象的引用,这也意味着集合中的元素必须为可哈希类型。

查找元素对象时,先通过算法定位数组索引,继而比较哈希值和内容。

集合对象自带一个长度为 8 的小数组(small table),这对多数简单集合运算有益,可避免额外的内存分配。只有超出容量限制时,才分配更大的数组内存(entry table)。集合使用的频率没有列表和字典高,内部没有采用缓存服用策略,其实现方式决定了无序存储,标准库也没有提供有序实现。

创建

创建一个集合对象可以和字典一样使用大括号的语法,但初始化数据用非键值对;也可以调用类型构建方法,或使用推导式。 集合允许在不同的版本进行转换。

>>> s = {1}
>>> f = frozenset(s)
>>> set(f)
{1}

操作

集合支持大小、相等运算符。

>>> {1, 2} > {2, 1}
False
>>> {1, 2} == {2, 1}
True

但子集判断不能使用 in、not in 语句,因为其只能用来检查是否包含某个元素。

>>> {1, 2} <= {1, 2, 3}	# 子集:issubset
True
>>> {1, 2, 3} >= {1, 2}	# 超集:issuperset
True
>>> {1, 2} in {1, 2, 3}	# 判断是否包含 {1, 2} 单一元素
False

集合是初等数学中的概念,其重点自然是并差集运算,合理使用这些操作,可简化算法筛选逻辑,使其具备更好的可读性。

交集(&):同时属于 A、B 两个集合的元素
并集(|):A、B 的所有元素
差集(-):仅属于 A、不属于 B 的元素
对称差集(^):仅属于 A,加上仅属于 B 的元素,相当于“并集-交集”

集合支持删除操作,但 remove 可能会引发异常,可改用 discard。

>>> x = {1, 2}
>>> x.remove(2)
>>> x.remove(2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 2

>>> x.discard(2)

自定义类型

自定义类型虽然是可哈希类型,但默认实现并不足以完成集合的去重操作。

class User:
    def __init__(self, uid, name):
        self.uid = uid
        self.name = name
>>> import collections
>>> issubclass(User, collections.Hashable)
True

>>> u1 = User(1, "user1")
>>> u2 = User(1, "user1")
>>> s = set()
>>> s.add(u1)
>>> s.add(u2)
>>> s
{<__main__.User object at 0x10bc1df98>, <__main__.User object at 0x10bc29048>}

其根本原因是默认实现的__hash__方法返回随机值,而__eq__仅比较自身,为符合逻辑需要,须重载这两个方法。

class User:
    def __init__(self, uid, name):
        self.uid = uid
        self.name = name

    def __hash__(self):		# 针对 uid 去重,忽略其他字段
        return hash(self.uid)

    def __eq__(self, other):
        return self.uid == other.uid
>>> u1 = User(1, "user1")
>>> u2 = User(1, "user1")
>>> s = set()
>>> s.add(u1)
>>> s.add(u2)
>>> s
{<__main__.User object at 0x10bc1df98>}
>>> u1 in s
True
>>> u2 in s		# 仅检查 uid 字段
True
comments powered by Disqus