Python 進階之路 (四) 先立Flag, 社群最全的Set用法集錦

weixin_33806914發表於2019-02-09

Set是什麼

大家好,恰逢初五迎財神,先預祝大家新年財源滾滾!!
在上一期詳解tuple元組的用法後,今天我們來看Python裡面最後一種常見的資料型別:集合(Set)

與dict類似,set也是一組key的集合,但不儲存value。由於key不能重複,所以,在set中,沒有重複的key。建立一個set,需要提供一個list作為輸入集集合,重複元素在set中會被自動被過濾,通過add(key)方法往set中新增元素,重複新增不會有效果。如果現在你發現我講的很模糊請不要著急。稍後會有海量例子為大家詳解。

總而言之,Set具有三個顯著特點:

  • 無序
  • 元素是獨一無二的,不允許出現重複的元素
  • 可以修改集合本身,但集合中包含的元素必須是不可變型別

現在讓我們開啟Set奇幻之旅,我希望這篇文章是SegmentFault社群對於Set介紹最全的模範,哈哈!

定義一個Set

我們有兩種方式可以建立一個Set,可以使用內建的set()方法,或是使用中括號{}
建立模板如下:

                    x = set(<iter>)         
                    x = {<obj>, <obj>, ..., <obj>}

現在讓我們來看例子~

set()內建方法建立
x = set(['foo', 'bar', 'baz', 'foo', 'qux'])   # 傳入List
print(x)

y = set(('foo', 'bar', 'baz', 'foo', 'qux'))   #傳入元組
print(y)

Out: {'qux', 'foo', 'bar', 'baz'}  # 注意到無序了吧~
     {'bar', 'qux', 'baz', 'foo'}
     

這裡要注意用set()內建方法建立時一定要傳遞一個可以迭代的引數,還有從輸出結果相信大家已經發現set的第一個特點了:無序

字串也是可迭代的,因此字串也可以傳遞給set()

s = 'quux'
a = set(s)
print(a)

Out: {'u', 'q', 'x'}      # 無序,唯一

這裡又體現了set的第二個特點:元素唯一性

{} 方法建立
>>> x = {'foo', 'bar', 'baz', 'foo', 'qux'}
>>> x
{'qux', 'foo', 'bar', 'baz'}

這裡考慮到之後例子太多,實在不能每次都打print啦,這種形式大家看的更清楚,這個直接用{}建立很簡單,只要傳遞進元素就行啦

建立空集合

Set可以是空的。但是,請記住Python將空花括號{}解釋為空字典,因此定義空集的唯一方法是使用set()函式

>>> x = set()
>>> type(x)
<class 'set'>

>>> x = {}
>>> type(x)
<class 'dict'>

一個空集合用布林型別顯示為False

>>> x = set()
>>> bool(x)
False
>>> x or 1
1
>>> x and 1
set()
對比小結

對於這兩種方法建立Set,本質區別在於以下兩點

  1. set()的引數是可迭代的。它會生成要放入集合中的所有元素組成的List。
  2. 花括號 {} 中的物件完整地放入集合中,即使它們是可迭代的。
補充說明

集合中的元素可以是不同型別的物件,不一定非要是同一型別的,可以包含不同型別,比如:

>>> x = {42, 'foo', 3.14159, None}
>>> x
{None, 'foo', 42, 3.14159}

但同時不要忘記set元素必須是不可變的。例如,元組可以包括在集合中:

>>> x = {42, 'foo', (1, 2, 3), 3.14159}
>>> x
{42, 'foo', 3.14159, (1, 2, 3)}

但列表和字典是可變的,因此它們不能成為Set的元素:

>>> a = [1, 2, 3]
>>> {a}
Traceback (most recent call last):
  File "<pyshell#70>", line 1, in <module>
    {a}
TypeError: unhashable type: 'list'


>>> d = {'a': 1, 'b': 2}
>>> {d}
Traceback (most recent call last):
  File "<pyshell#72>", line 1, in <module>
    {d}
TypeError: unhashable type: 'dict'

Set大小以及成員

len()函式返回集合中元素的數量,而in和not in運算子可用於測試是否為Set中的元素:

>>> x = {'foo', 'bar', 'baz'}
>>> len(x)
3

>>> 'bar' in x
True
>>> 'qux' in x
False

Set基本操作

方法和運算子

許多可用於Python其他資料型別的操作對集合沒有意義。例如,無法對集合建立索引或切片。但是,Python在set物件上提供了運算子,這些操作符其實很多和數學裡是一模一樣的,相信數學好的朋友們對這部分簡直不要太熟悉

所以對於Set的操作除了用普通的內建方法,我們也可以使用運算子,比較方便

Union 並集
  • 用法:計算兩個或更多集合的並集。
  • 方法: x1.union(x2[, x3 ...])
  • 運算子:x1 | x2 [| x3 ...]

讓我們新建兩個Set做測試:

>>> x1 = {'foo', 'bar', 'baz'}
>>> x2 = {'baz', 'qux', 'quux'}

現在我們想求x1,x2的並集,如下圖所示:

clipboard.png

具體實現方法如下,或是用方法,或是用操作符:

>>> x1 = {'foo', 'bar', 'baz'}
>>> x2 = {'baz', 'qux', 'quux'}

>>> x1.union(x2)
{'foo', 'qux', 'quux', 'baz', 'bar'}

>>> x1 | x2
{'foo', 'qux', 'quux', 'baz', 'bar'}

如果有兩個以上的Set也是沒有問題的,原理都是一樣的:

>>> a = {1, 2, 3, 4}
>>> b = {2, 3, 4, 5}
>>> c = {3, 4, 5, 6}
>>> d = {4, 5, 6, 7}

>>> a.union(b, c, d)
{1, 2, 3, 4, 5, 6, 7}

>>> a | b | c | d
{1, 2, 3, 4, 5, 6, 7}
Intersection 交集
  • 方法: x1.intersection(x2[, x3 ...])
  • 運算子:x1 & x2 [& x3 ...]
  • 用法:計算兩個或更多集合的交集。

現在還讓我們用剛才建立好的兩個set,所求部分如下圖:

clipboard.png

實現仍然是兩種方法:

>>> x1 = {'foo', 'bar', 'baz'}
>>> x2 = {'baz', 'qux', 'quux'}

>>> x1.intersection(x2)
{'baz'}

>>> x1 & x2
{'baz'}

多個集合的情況公示和方法依然有效,結果僅包含所有指定集合中都存在的元素。

>>> a = {1, 2, 3, 4}
>>> b = {2, 3, 4, 5}
>>> c = {3, 4, 5, 6}
>>> d = {4, 5, 6, 7}

>>> a.intersection(b, c, d)
{4}

>>> a & b & c & d
{4}
Difference 差集
  • 方法: x1.difference(x2[, x3 ...])
  • 運算子:x1 - x2 [- x3 ...]
  • 用法:計算兩個或更多集合的差集。大白話說就是x1去除x1和x2的共有元素

下圖所示為x1.difference(x2)的目標結果:
clipboard.png

>>> x1 = {'foo', 'bar', 'baz'}
>>> x2 = {'baz', 'qux', 'quux'}

>>> x1.difference(x2)
{'foo', 'bar'}

>>> x1 - x2
{'foo', 'bar'}

還是老樣子,適用於2個及以上的集合:

>>> a = {1, 2, 3, 30, 300}
>>> b = {10, 20, 30, 40}
>>> c = {100, 200, 300, 400}

>>> a.difference(b, c)
{1, 2, 3}

>>> a - b - c
{1, 2, 3}

指定多個集合時,操作從左到右執行。在上面的示例中,首先計算a - b,得到{1,2,3,300}。然後從該集合中減去c,留下{1,2,3},具體流程如下圖所示:

clipboard.png

Symmetric Difference 對稱差集
  • 方法: x1.symmetric_difference(x2)
  • 運算子:x1 ^ x2 1
  • 用法:計算兩個或更多集合的差集。大白話說就是x1去除x1和x2的共有元素

下圖所示為x1.symmetric_difference(x2)的目標結果:

clipboard.png

實現方法如下;

>>> x1 = {'foo', 'bar', 'baz'}
>>> x2 = {'baz', 'qux', 'quux'}

>>> x1.symmetric_difference(x2)
{'foo', 'qux', 'quux', 'bar'}

>>> x1 ^ x2
{'foo', 'qux', 'quux', 'bar'}

老規矩,支援2個及以上set的連續操作:

>>> a = {1, 2, 3, 4, 5}
>>> b = {10, 2, 3, 4, 50}
>>> c = {1, 50, 100}

>>> a ^ b ^ c
{100, 5, 10}

當指定多個集合時,操作從左到右執行,奇怪的是,雖然 ^ 運算子允許多個集合,但.symmetric_difference()方法不允許

>>> a = {1, 2, 3, 4, 5}
>>> b = {10, 2, 3, 4, 50}
>>> c = {1, 50, 100}

>>> a.symmetric_difference(b, c)
Traceback (most recent call last):
  File "<pyshell#11>", line 1, in <module>
    a.symmetric_difference(b, c)
TypeError: symmetric_difference() takes exactly one argument (2 given)
x1.isdisjoint(x2) 判斷是否相交
  • 方法: x1.isdisjoint(x2)
  • 用法:確定兩個集合是否具有任何共同的元素
>>> x1 = {'foo', 'bar', 'baz'}
>>> x2 = {'baz', 'qux', 'quux'}

>>> x1.isdisjoint(x2)
False

>>> x2 - {'baz'}
{'quux', 'qux'}
>>> x1.isdisjoint(x2 - {'baz'})
True

從這個栗子可以看出,如果兩個Set沒有共同元素返回True,如果有返回True,如果返回True同時也意味著
他們之間的交集為空集,這個很好理解:

>>> x1 = {1, 3, 5}
>>> x2 = {2, 4, 6}

>>> x1.isdisjoint(x2)
True
>>> x1 & x2
set()

注意:目前還沒有運算子對應這個方法

x1.issubset(x2) 判斷x1是否為x2子集
  • 方法: x1.issubset(x2)
  • 運算子:x1 <= x2
  • 用法:如果返回True,x1為x2子集,反之返回False
>>> x1 = {'foo', 'bar', 'baz'}
>>> x1.issubset({'foo', 'bar', 'baz', 'qux', 'quux'})
True

>>> x2 = {'baz', 'qux', 'quux'}
>>> x1 <= x2
False

一個集合本身當然是它自己的子集啦:

>>> x = {1, 2, 3, 4, 5}
>>> x.issubset(x)
True
>>> x <= x
True
x1<x2 判斷x1是否為x2的真子集
  • 運算子:x1<x2
  • 用法:判斷x1是否為x2的真子集,如果返回True,x1為x2的真子集,反之返回False

首先。。。讓我們回顧一下數學知識:真子集與子集類似,除了集合不能相同。如果x1的每個元素都在x2中,並且x1和x2不相等,則集合x1被認為是另一個集合x2的真子集

換個高大上的說法也可以:如果集合A⊆B,存在元素x∈B,且元素x不屬於集合A,我們稱集合A與集合B有真包含關係,集合A是集合B的真子集(proper subset)。記作A⊊B(或B⊋A),讀作“A真包含於B”(或“B真包含A”)

>>> x1 = {'foo', 'bar'}
>>> x2 = {'foo', 'bar', 'baz'}
>>> x1 < x2
True

>>> x1 = {'foo', 'bar', 'baz'}
>>> x2 = {'foo', 'bar', 'baz'}
>>> x1 < x2
False

雖然Set被認為是其自身的子集,但它本身並不是自己的真子集:

>>> x = {1, 2, 3, 4, 5}
>>> x <= x
True
>>> x < x
False

注意:目前還沒有方法對應這個運算子

x1.issuperset(x2) 判斷x1是否為x2的超集
  • 方法:x1.issuperset(x2)
  • 運算子:x1 >= x2
  • 用法:判斷x1是否為x2的超集,如果是返回True,反之返回False
>>> x1 = {'foo', 'bar', 'baz'}

>>> x1.issuperset({'foo', 'bar'})
True

>>> x2 = {'baz', 'qux', 'quux'}
>>> x1 >= x2
False

我們剛才已經看到過了一個Set是它自己本身的子集,這裡也是一樣的,它同時也是自己的超集

>>> x = {1, 2, 3, 4, 5}
>>> x.issuperset(x)
True
>>> x >= x
True
x1 > x2 判斷x1是否為x2的真超集
  • 運算子:x1 > x2
  • 用法:判斷x1是否為x2的真超集,如果是返回True,反之返回False

真超集與超集相同,除了集合不能相同。如果x1包含x2的每個元素,並且x1和x2不相等,則集合x1被認為是另一個集合x2的真超集。

>>> x1 = {'foo', 'bar', 'baz'}
>>> x2 = {'foo', 'bar'}
>>> x1 > x2
True

>>> x1 = {'foo', 'bar', 'baz'}
>>> x2 = {'foo', 'bar', 'baz'}
>>> x1 > x2
False

一個集合不是它自己的真超集,和真子集的原理相同

>>> x = {1, 2, 3, 4, 5}
>>> x > x
False

對Set進行修改

雖然集合中包含的元素必須是不可變型別,但可以修改集合本身。與上面的操作類似,可以使用多種運算子和方法來更改集合的內容。

x1.update(x2) 通過union修改集合元素
  • 方法:x1.update(x2[, x3 ...])
  • 運算子:x1 |= x2 [| x3 ...]
  • 用法:通過union修改集合

x1.update(x2) 和 x1 |= x2 作用是向集合x1中新增x2中所有x1不存在的元素。
停下3秒,我仔細讀了這句話,覺得我表達的還可以,不知道大家讀上去繞不繞,先看例子:

>>> x1 = {'foo', 'bar', 'baz'}
>>> x2 = {'foo', 'baz', 'qux'}

>>> x1 |= x2
>>> x1
{'qux', 'foo', 'bar', 'baz'}

>>> x1.update(['corge', 'garply'])
>>> x1
{'qux', 'corge', 'garply', 'foo', 'bar', 'baz'}
x1.intersection(x2) 通過intersection修改集合元素
  • 方法:x1.intersection_update(x2[, x3 ...])
  • 運算子:x1 &= x2 [& x3 ...]
  • 用法:通過intersection修改集合

x1.intersection_update(x2) 和 x1 &= x2 會讓x1只保留x1和x2的交集部分:

>>> x1 = {'foo', 'bar', 'baz'}
>>> x2 = {'foo', 'baz', 'qux'}

>>> x1 &= x2
>>> x1
{'foo', 'baz'}

>>> x1.intersection_update(['baz', 'qux'])
>>> x1
{'baz'}
x1.difference_update(x2) 通過difference修改集合元素
  • 方法:x1.difference_update(x2[, x3 ...])
  • 運算子:x1 -= x2 [| x3 ...]
  • 用法:通過difference修改集合

x1.difference_update(x2) and x1 -= x2 會讓集合x1移除所有在x2出現的屬於x1的元素:

>>> x1 = {'foo', 'bar', 'baz'}
>>> x2 = {'foo', 'baz', 'qux'}

>>> x1 -= x2
>>> x1
{'bar'}

>>> x1.difference_update(['foo', 'bar', 'qux'])
>>> x1
set()
x1.symmetric_difference_update(x2) 通過對稱差集修改集合元素
  • 方法:x1.symmetric_difference_update(x2)
  • 運算子:x1 ^= x2

這個我實在用語言解釋不清了,看例子容易懂:

>>> x1 = {'foo', 'bar', 'baz'}
>>> x2 = {'foo', 'baz', 'qux'}
>>> 
>>> x1 ^= x2
>>> x1
{'bar', 'qux'}
>>> 
>>> x1.symmetric_difference_update(['qux', 'corge'])
>>> x1
{'bar', 'corge'}
x.add(<elem> 新增元素

這個就很簡單了, 類似List:

>>> x = {'foo', 'bar', 'baz'}
>>> x.add('qux')
>>> x
{'bar', 'baz', 'foo', 'qux'}
x.remove(<elem>) 刪除元素

如果刪除的元素不存在會丟擲異常

>>> x = {'foo', 'bar', 'baz'}

>>> x.remove('baz')
>>> x
{'bar', 'foo'}

>>> x.remove('qux')
Traceback (most recent call last):
  File "<pyshell#58>", line 1, in <module>
    x.remove('qux')
KeyError: 'qux'

這個時候為了避免出現錯誤可以用discard方法

>>> x = {'foo', 'bar', 'baz'}

>>> x.discard('baz')
>>> x
{'bar', 'foo'}

>>> x.discard('qux')
>>> x
{'bar', 'foo'}

利用pop刪除隨機元素並返回:

>>> x = {'foo', 'bar', 'baz'}

>>> x.pop()
'bar'
>>> x
{'baz', 'foo'}

>>> x.pop()
'baz'
>>> x
{'foo'}

>>> x.pop()
'foo'
>>> x
set()

利用clear可以清空一個集合:

>>> x = {'foo', 'bar', 'baz'}
>>> x
{'foo', 'bar', 'baz'}
>>> 
>>> x.clear()
>>> x
set()

Frozen Sets

Frozen Sets是什麼東西

Python提供了另一種稱為凍結集合Frozen Sets的內建型別,它在所有方面都與集合完全相同,只不過Frozen Sets是不可變的。我們可以對凍結集執行非修改操作,比如:

>>> x = frozenset(['foo', 'bar', 'baz'])
>>> x
frozenset({'foo', 'baz', 'bar'})

>>> len(x)
3

>>> x & {'baz', 'qux', 'quux'}
frozenset({'baz'})

如果膽敢嘗試修改Frozen Sets:

>>> x = frozenset(['foo', 'bar', 'baz'])

>>> x.add('qux')
Traceback (most recent call last):
  File "<pyshell#127>", line 1, in <module>
    x.add('qux')
AttributeError: 'frozenset' object has no attribute 'add'

>>> x.pop()
Traceback (most recent call last):
  File "<pyshell#129>", line 1, in <module>
    x.pop()
AttributeError: 'frozenset' object has no attribute 'pop'

>>> x.clear()
Traceback (most recent call last):
  File "<pyshell#131>", line 1, in <module>
    x.clear()
AttributeError: 'frozenset' object has no attribute 'clear'

>>> x
frozenset({'foo', 'bar', 'baz'})
基本使用舉例

Frozensets在我們想要使用集合的情況下很有用,但需要一個不可變物件。
例如,如果沒有Frozen sets我們不能定義其元素也是集合的集合(nested),因為集合元素必須是不可變的,會報錯:

>>> x1 = set(['foo'])
>>> x2 = set(['bar'])
>>> x3 = set(['baz'])
>>> x = {x1, x2, x3}
Traceback (most recent call last):
  File "<pyshell#38>", line 1, in <module>
    x = {x1, x2, x3}
TypeError: unhashable type: 'set'

現在有了 Frozen sets,我們有了解決方案:

>>> x1 = frozenset(['foo'])
>>> x2 = frozenset(['bar'])
>>> x3 = frozenset(['baz'])
>>> x = {x1, x2, x3}
>>> x
{frozenset({'bar'}), frozenset({'baz'}), frozenset({'foo'})}

總結

這一期為大家講了太多東西,一口老血吐在鍵盤上,總結不動了
只希望這期Set詳解介紹可以幫助到大家,如果幫到了你,就點個贊吧~~
最後再次祝大家豬年大吉!!


  1. x3 ...

相關文章