流畅的Python:数据模型之集合

集合set

学习《流畅的Python》,后续系列博客大部分均摘自本书,仅用于交流、学习和记录。

集合的本质是许多唯一对象的聚集。因此,集合可以用于去重。

列表、字典、集合等不可散列的对象是不能用来作为集合的元素的,不可变的对象如字符串、元组等可散列才可以。

set([1,2,3,[4,5]])
Traceback (most recent call last):
  File "D:\Python3.6.0\lib\site-packages\IPython\core\interactiveshell.py", line 2961, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-3-97f17f7a499c>", line 1, in <module>
    set([1,2,3,[4,5]])
TypeError: unhashable type: 'list'
    
set([1,2,3,{'4':5}])
Traceback (most recent call last):
  File "D:\Python3.6.0\lib\site-packages\IPython\core\interactiveshell.py", line 2961, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-5-eae42bf28a0e>", line 1, in <module>
    set([1,2,3,{'4':5}])
TypeError: unhashable type: 'dict'
    
set([1,2,3,{1,2,3}])
Traceback (most recent call last):
  File "D:\Python3.6.0\lib\site-packages\IPython\core\interactiveshell.py", line 2961, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-12-435ddc9ed94f>", line 1, in <module>
    set([1,2,3,{1,2,3}])
TypeError: unhashable type: 'set'
    
set([1,2,3,(4,5)])
Out[4]: {(4, 5), 1, 2, 3}
  
set([1,2,"123"])
Out[5]: {1, '123', 2}
  
item = frozenset([1,2,3])
set([1,2,3,item])
Out[11]: {1, 2, 3, frozenset({1, 2, 3})}
li = [1,2,3,4,4]
set(li)
Out[6]: {1, 2, 3, 4}

集合中的元素必须是可散列的,set 类型本身是不可散列的,但是 frozenset 可以。因此可以创建一个包含不同 frozenset 的 set。

除了保证唯一性,集合还实现了很多基础的中缀运算符 。给定两个集合 a 和 b,a | b 返回的是它们的合集,a & b 得到的是交集,而 a - b 得到的是差集。合理地利用这些操作,不仅能够让代码的行数变少,还能减少 Python 程序的运行时间。这样做同时也是为了让代码更易读,从而更容易判断程序的正确性,因为利用这些运算符可以省去不必要的循环和逻辑操作。

集合字面量:

除空集之外,集合的字面量——{1}、{1, 2},等等——看起来跟它的数学形式一模一样。如果是空集,那么必须写成 set() 的形式。如果要创建一个空集,你必须用不带任何参数的构造方法 set()。如果只是写成 {} 的形式,跟以前一样,你创建的其实是个空字典。

集合的推导:

from unicodedata import name
s = {chr(i) for i in range(32, 256) if 'SIGN' in name(chr(i), '')}
s
{'#', '$', '%', '+', '<', '=', '>', '¢', '£', '¤', '¥', '§', '©', '¬', '®', '°', '±', 'µ', '¶', '×', '÷'}

集合的数学运算:

数学符号 运算符 方法 描述
S ∩ Z s & z s.__and__(z) s和z的交集
z & s s.__rand__(z) 反向 & 操作
s.intersection(it, …) 把可迭代的 it 和其他所有参数转化为集合,然后求它们与 s 的交集
s &= z s.__iand__(z) 把 s 更新为 s 和 z 的交集
s.intersection_update(it, …) 把可迭代的 it 和其他所有参数转化为集合,然后求得它们与 s 的交集,然后把 s 更新成这个交集
S ∪ Z s | z s.__or__(z) s 和 z 的并集
z | s s.__ror__(z) |的反向操作
s.union(it, …) 把可迭代的 it 和其他所有参数转化为集合,然后求它们和 s 的并集
s |= z s.__ior__(z) 把 s 更新为 s 和 z 的并集
s.update(it, …) 把可迭代的 it 和其他所有参数转化为集合,然后求它们和 s 的并集,并把 s 更新成这个并集
S \ Z s - z s.__sub__(z) s 和 z 的差集,或者叫作相对补集
z - s s.__rsub__(z) - 的反向操作
s.difference(it, …) 把可迭代的 it 和其他所有参数转化为集合,然后求它们和 s 的差集
s -= z s.__isub__(z) 把 s 更新为它与 z 的差集
s.difference_update(it, …) 把可迭代的 it 和其他所有参数转化为集合,求它们和 s 的差集,然后把 s 更新成这个差集
s.symmetric_difference(it) 求 s 和 set(it) 的对称差集
S ∆ Z s ^ z s.__xor__(z) 求 s 和 z 的对称差集
z ^ s s.__rxor__(z) ^ 的反向操作
s.symmetric_difference_update(it, …) 把可迭代的 it 和其他所有参数转化为集合,然后求它们和 s 的对称差集,最后把 s 更新成该结果
z ^= s s.__ixor__(z) 把 s 更新成它与 z 的对称差集

集合的比较运算:

数学符号 运算符 方法 描述
s.isdisjoint(z) 查看 s 和 z 是否不相交(没有共同元素)
e ∈ S e in s s.__contains__(e) 元素 e 是否属于 s
S ⊆ Z s <= z s.__le__(z) s 是否为 z 的子集
s.issubset(it) 把可迭代的 it 转化为集合,然后查看 s 是否为它的子集
S ⊂ Z s < z s.__lt__(z) s 是否为 z 的真子集
S ⊇ Z s >= z s.__ge__(z) s 是否为 z 的父集
s.issuperset(it) 把可迭代的 it 转化为集合,然后查看 s 是否为它的父集,然后查看 s 是否为它的父集
S ⊃ Z s > z s.__gt__(z) s 是否为 z 的真父集

散列表其实是一个稀疏数组(总是有空白元素的数组称为稀疏数组)。在一般的数据结构教材中,散列表里的单元通常叫作表元(bucket)。在 dict 的散列表当中,每个键值对都占用一个表元,每个表元都有两个部分,一个是对键的引用,另一个是对值的引用。因为所有表元的大小一致,所以可以通过偏移量来读取某个表元。

因为 Python 会设法保证大概还有三分之一的表元是空的,所以在快要达到这个阈值的时候,原有的散列表会被复制到一个更大的空间里面。 如果要把一个对象放入散列表,那么首先要计算这个元素键的散列值。Python 中可以用hash() 方法来做这件事情。

由于字典使用了散列表,而散列表又必须是稀疏的,这导致它在空间上的效率低下。如果需要存放数量巨大的记录,那么放在由元组或是具名元组构成的列表中会是比较好的选择;最好不要根据 JSON 的风格,用由字典组成的列表来存放这些记录。用元组取代字典就能节省空间的原因有两个:其一是避免了散列表所耗费的空间,其二是无需把记录中字段的名字在每个元素里都存一遍。

set的实现以及导致的结果:

set 和 frozenset 的实现也依赖散列表,但在它们的散列表里存放的只有元素的引用(就像在字典里只存放键而没有相应的值)。在 set 加入到 Python 之前,都是把字典加上无意义的值当作集合来用的。


字典和散列表的几个特点,对集合来说几乎都是适用的:

  • 集合里的元素必须是可散列的。

  • 集合很消耗内存。

  • 可以很高效地判断元素是否存在于某个集合。

  • 元素的次序取决于被添加到集合里的次序。

  • 往集合里添加元素,可能会改变集合里已有元素的次序。

dict 和 set 背后的散列表效率很高,对它的了解越深入,就越能理解为什么被保存的元素会呈现出不同的顺序,以及已有的元素顺序会发生变化的原因。同时,速度是以牺牲空间为代价而换来的。

发布了87 篇原创文章 · 获赞 43 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/qq_31362767/article/details/102986676