1. 程式人生 > 其它 >Python內建資料結構之集合

Python內建資料結構之集合

今天給大家介紹內建資料結構集合的用法。

看一下集合的思維導圖:

集合的特點

  1. 元素是唯一的
  2. 元素是無序的,不是線性結構
  3. 集合元素是可hash的
  4. 聚合的含義和數學上的含義相同

集合的操作

  1. 增:addupdate
  2. 刪:remove,discard,clear,pop
  3. 集合運算:union,intersection,difference, symmetric_difference
  4. 集合判斷:issubset,issupperset,isdisjoint

具體例項

在Python中怎麼定義一個集合呢?

s = set()  # 使用內建的set方法
>>> s = set()
>>> s
set()
>>> type(s)
<class 'set'>
# 可以給集合賦初值
s = {1, 2, 3} # 使用大括號

# 但不能使用如下形式的定義集合
s = {} # 不能這麼定義集合,使用type方法檢視s的型別
>>> s = {}
>>> type(s)
<class 'dict'>
s = {1, 2, 3}
type(s)
set

add方法,

s = {1, 2, 3}
s
s.add(4)
s
s.add(4)
>>> s.add('a')
>>> s
{'a', 1, 2, 3, 4}

# add一個列表
>>> s.add([4, 5, 6])
TypeError: unhashable type: 'list'
# 為什麼不能增加一個列表呢?由於集合使用hash來判斷元素是否重複;
# 由於列表是不能hash的,所以,集合的add方法不能增加一個列表到
# 已有的集合中

# add一個字串
>>> s.add('abcde')
>>> s
{1, 2, 3, 4, 'a', 'abcde'}

# add一個元組
>>> s.add((1, 2, 3))
>>> s
{1, 2, 3, 4, (1, 2, 3), 'a', 'abcde'}

# add一個類的例項
class A:
    pass

a = A()
hash(a)

當add一個已經存在的元素時,不會發生任何改變。

重要 為什麼不能增加一個列表呢?由於集合使用hash來判斷元素是否重複;由於列表是不能hash的,所以,集合的add方法不能增加一個列表到已有的集合中。內建資料型別中,可變的都是不可雜湊的,而不可變的型別是可雜湊的。list,set,bytearray,dict是不可hash的,所以不能作為set的元素;通常來說,內建型別,不可變型別是可hash的,可變型別是不可hash的。

update方法,

s = {1, 2, 3, 4}
s.update([3, 4, 5, 6])
s # 已經把重複的元素去重了
set([1, 2, 3, 4])

# 使用set去重一個list
list(set([1, 3, 2, 1, 2, 4]))

刪除的方法,

# remove方法,但要移除的元素必須存在
s = {1, 2, 3, 4}
s.remove(1)  # 移除一個存在的
s.remove(10) # 移除一個不存在的

# discard方法,不要求要刪除的元素是否存在於集合中
s.discard(2)  # 移除一個存在的
s.discard(20) # 移除一個不存在的

# pop方法,會隨機移除一個元素,但要求集合為非空
s = {3, 4, 5, 6}
s.pop()
s

# clear方法,清除集合中的所有元素
s.clear()

刪除集合元素總結:

  1. remove刪除指定的元素,元素不存在丟擲KeyError
  2. discard刪除指定的元素,元素不存在,什麼也不做
  3. pop隨機刪除一個元素並返回,集合為空,丟擲KeyError
  4. clear清空集合

集合的修改和查詢,

沒有一個方法可以直接的修改集合中的某個具體元素;因為沒有一個方法,可以定位
其中的某個具體元素。

集合不能通過索引訪問。集合沒有訪問單個元素的方法。集合不是線性結構,集合元素沒有順序。

集合也是可迭代的物件,

for x in set('lavenliu'):
    print(x)

集合可以用成員運演算法,線性結構的成員運算,時間複雜度是O(n),集合的成員運算,時間複雜度是O(1)。

'l' in set('lavenliu')

集合的成員運算和其他線性結構的時間複雜度不同。做成員運算的時候,集合的效率遠高於列表。集合的效率和集合的規模無關。

In[7]: lst = list(range(1000000))

In[8]: %%timeit
   ...: -1 in lst
   ...: 
100 loops, best of 3: 13.7 ms per loop

In[9]: lst = list(range(100000))

In[10]: %%timeit
    ...: -1 in lst
    ...: 
1000 loops, best of 3: 1.39 ms per loop

In[11]: s2 = set(range(1000000))

In[12]: %%timeit
    ...: -1 in s2
    ...: 
10000000 loops, best of 3: 57.5 ns per loop

In[13]: s2 = set(range(100))

In[14]: %%timeit
    ...: -1 in s2
    ...: 

10000000 loops, best of 3: 58.4 ns per loop

In[15]: 

時間複雜度:

O(1)    常數複雜度
O(logn) 對數複雜度
O(n)    線性複雜度
O(n^2)  平方複雜度
O(n^3)  立方複雜度
O(2^n)  指數複雜度
O(n!)   階乘複雜度

集合的集合運算

集合的運算:

  1. 並集union
  2. 交集intersection
  3. 差集difference
  4. 對稱差集symmetric_difference

存在集合A和B,對於集合C,如果C的每個元素既是A的元素,又是B的元素,並且A和B所有相同的元素都在C找到,那麼C是A和B的交集。

集合A和B,當集合C的元素僅存在A中,但不存在B中,並且A中存在B中不存在的元素全部存在C中,那麼C是A和B的差集。

如果把兩個集合A和B看成是一個全集,對稱差集是交集的補集。

例項演示,

a = {1, 2, 3}
b = {2, 3, 4}

# 並集
a.union(b)
# 集合過載了按位或運算子,用於集合的並集運算
a | b
# 並集的update版本
a.update(b)
a # {1, 2, 3, 4}

# 交集,不修改原來的集合,會返回新的集合
# 集合的交集運算,過載按位與運算子為交集運算。
# a.intersection(b) 等效於 a & b
a.intersection(b)
a & b # {2, 3}

a.instersection_update(b) # instersection_update版本會原地修改,返回None
# a = a.insertsection(b)
# a = {2, 3}
# b = {2, 3, 4}

# 差集
# 差集沒有交換律
a.difference(b)
a - b # {1}

# 差集也有update版本
a.difference_update(b) # 相當於a = a.difference(b)
a # {1}

# 集合的差集運算,過載減法運算子為交集運算。
# a.difference(b) 等效於 a - b
a - b # {1}

# 對稱差集
# 對稱差集具有交換律
a.symmetric_difference(b)
# 集合的差集運算,過載異或運算子為對稱差集運算。
a ^ b # {1, 4} # 得到的結果是a和b的非相同元素,(a | b) - (a & b)

# 對稱差集也有update版本
a.symmetric_difference_update(b) # 原地修改,返回None。相當於a = a.symmetric_difference(b)


# 其他一些特性
a.union(b) == b.union(a)
a.intersection(b) == b.intersection(a)
a.difference(b) == b.difference(a)
a.symmetric_difference == b.symmetric_difference(a)

集合相關的判斷。超集與子集,isuperset,issubset

a = {1, 2, 3, 4}
b = {3, 4}
a.issuperset(b)
b.issubset(a)
a.issuperset(a)
b.issubset(a)

isdisjoint方法判斷兩個集合是否不相交,如果有交集返回False,沒有交集返回True。

a.isdisjoint(b)
a.isdisjoint({5, 6})

一個例子:

def issubset(s1, s2):
    for x in s1:
        if x not in s2:
            return False
    return True

def issuperset(s1, s2):
    for x in s2:
        if x not in s1:
            return False
    return True

集合的應用

  1. 元素需要唯一,而對順序沒有要求
  2. 需要集合運算時

舉幾個具體場景的例子,

# 使用者輸入了一批主機,這些主機肯定不能重複,可以這樣來處理
# 這樣,就不會在機器上執行重複的操作了
hosts = set(user_input.splitlines())

# 得到還在集合中的機器列表
in_progress_hosts = hosts - complete_hosts

# 獲得使用者的所有輸入的主機
hosts = input_a | input_b | input_c

# 對多個目錄下的檔案去重

有一個API,它要有認證,並且有一定許可權才可以訪問,例如,要求滿足許可權A,B,C中任意一項,有一個使用者具有許可權B,C,D,那麼此使用者是否有許可權訪問此API。(判斷是否集合是否相交,返回False說明相交,說明具有訪問許可權)

有一個任務列表,儲存全部的任務,有一個列表,儲存已經完成的任務,找出未完成的任務。

集合的限制

  1. 列表不能作為集合的元素
  2. bytearray不能作為集合的元素
  3. 集合不能作為集合的元素
  4. 元組與bytes可以作為集合的元素

可變元素不能成為集合的元素。集合元素必須可hash。目前我們所知道的所有可變的資料型別是不可hash的,所有的不可變的資料型別都是可hash的。