Python原生資料結構增強模組collections

金色旭光發表於2022-01-06

collections簡介

python提供了4種基本的資料結構:list、tuple、dict、set。基本資料結構完全可以hold住所有的場景,但是在處理資料結構複雜的場景時,這4種資料結構有時會顯的單一,比如將相同字母組成的字串歸類到列表中,是一個key為字串,value為列表的資料結構,複雜度為O(1)的情況下完成LRU(力扣原題)。
這個時候今天的主角collections包就可以登場了。collections是基本資料結構的高效能優化版,它提供了多個有用的集合類,熟練掌握這些集合類,不僅可以讓我們讓寫出的程式碼更加Pythonic,也可以提高我們程式的執行效率。

Counter

簡單使用

Counter是一個簡單的計數器,例如,統計字元出現的個數,並且會根據出現次數從大到小排列好:

>>> from collections import Counter
>>> Counter("hello world")
Counter({'l': 3, 'o': 2, ' ': 1, 'e': 1, 'd': 1, 'h': 1, 'r': 1, 'w': 1})
>>> 

功能:
Counter是一個計數器工具,提供快速和方便的計數。從型別上劃分,Counter是一個dict的子類,元素像字典一樣儲存,key是統計的元素,value是統計的個數。

Counter可統計多種資料型別,包括:字串,列表,字典,元組,集合等。

字串

>>> str = "hello world"
>>> 
>>> res = Counter(str)
>>> res
Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})

列表

>>> arr = [1,2,3,4,2,3,4,5]
>>> res = Counter(arr)
>>> res
Counter({2: 2, 3: 2, 4: 2, 1: 1, 5: 1})

字典

>>> map = {"a":3, "b":2, "c":1}
>>> res = Counter(map)
>>> res
Counter({'a': 3, 'b': 2, 'c': 1})

元組

>>> tu = (1,2,3,2,3,4,2,3,5)
>>> res = Counter(tu)
>>> res
Counter({2: 3, 3: 3, 1: 1, 4: 1, 5: 1})

集合

>>> Set = {1,2,3,3,4,5,4,5,6}
>>> Set
{1, 2, 3, 4, 5, 6}
>>> res = Counter(Set)
>>> res
Counter({1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1})

Counter擁有的方法

1.elements()
返回一個迭代器,其中每個元素將重複出現計數值所指定次。元素返回的順序是按照元素在原本序列中首次出現的順序

>>> str = "hello world"
>>> res = Counter(str)
>>> res
Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})
>>> list(res.elements())
['h', 'e', 'l', 'l', 'l', 'o', 'o', ' ', 'w', 'r', 'd']
>>> c = Counter(a=4, b=2, c=0, d=-2)
>>> list(c.elements())
['a', 'a', 'a', 'a', 'b', 'b']

2.most_common()
返回一個列表,其中包含n個出現次數最高的元素及出現次數,按常見程度由高到低排序。 如果 n 被省略或為None, 將返回計數器中的所有元素。
計數值相等的元素按首次出現的順序排序,經常用來計算top詞頻的詞語。

>>> str = "hello world"
>>> res = Counter(str)
>>> res
Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})
>>> res.most_common(2)
[('l', 3), ('o', 2)]

3.subtract()
將兩個Counter相減,a.sbutract(b),a中計數減少相應的個數

>>> from collections import Counter
>>> a = Counter("hello world")
>>> a
Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})
>>> b = Counter("hello")
>>> a.subtract(b)
>>> a
Counter({'l': 1, 'o': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1, 'h': 0, 'e': 0})

4.字典方法
通常字典方法都可用於Counter物件,除了有兩個方法工作方式與字典並不相同。

a. fromkeys(iterable)
這個類方法沒有在Counter中實現。

b. update([iterable-or-mapping])
Counter物件更新另一個物件。但是不同之處在於字典的update是更新元素的value,而Counter的update是將元素的統計相加。

defaultdict

defaultdict是帶預設值的字典,屬於內建字典的子集,它重寫了一個方法和新增一個可寫例項變數。普通字典在取出一個不存在的key時,會丟擲KeyError錯誤。如果希望在key不存在時返回一個預設值,可以使用字典的get方法,如map.get(key, None),或者是使用defaultdict。defaultdict作用就是在字典查詢不存在的key時返回一個預設值而不是KeyError。
功能:
為新增到字典中的每一個key增加一個預設值,當查詢一個不存在的元素時,會返回該預設型別和新增一個可寫例項變數。剩下的方法都和常規字典一樣。

預設值為列表

>>> dict_demo = defaultdict(list)
>>> arr = [("yello", 1),("blue", 2), ("green",3), ("yello", 9)]
>>> for key, value in arr:
...     dict_demo[key].append(value)
... 
>>> dict_demo
defaultdict(<class 'list'>, {'yello': [1, 9], 'blue': [2], 'green': [3]})

預設值為數字

>>> from collections import defaultdict
>>> dict_demo = defaultdict(int)
>>> count = [("a",1),("b",2),("c",3),("a",4)]
>>>
>>> for key, value in count:
...     dict_demo[key] += value
... 
>>> dict_demo
defaultdict(<class 'int'>, {'a': 5, 'b': 2, 'c': 3})

預設值的型別

dict_demo = defaultdict(str)  預設值:空字串
dict_demo = defaultdict(int)  預設值:0
dict_demo = defaultdict(list) 預設值:空列表
dict_demo = defaultdict(dict) 預設值:空字典
dict_demo = defaultdict(tuple) 預設值:空元素
dict_demo = defaultdict(set)  預設值:空集合

當訪問不存在的key時,會返回預設值,同時在字典中建立該記錄。

deque

在資料結構中,佇列和堆疊是兩個非常重要的資料型別,一個先進先出,一個後進先出。在python中,使用list儲存資料時,按索引訪問元素很快,但是插入和刪除元素就很慢了,因為list是線性儲存,資料量大的時候,插入和刪除效率很低。
deque是為了高效實現插入和刪除操作的雙向連結串列結構,非常適合實現佇列和堆疊這樣的資料結構

功能:
deque是一個棧和佇列的綜合,發音為deck,可以成為雙向佇列。deque支援執行緒安全,兩端都支援高效的入棧和出棧操作,時間複雜度為O(1)。
雖然list支援類似的操作,但它們針對快速的固定長度操作進行了優化,併為pop(0) 和insert(0,v)操作帶來了O(n)記憶體移動成本,這兩種操作都會更改基礎資料表示的大小和位置。
如果未指定maxlen或為None,則deque可能會增長到任意長度。否則,deque將繫結到指定的最大長度。一旦有界長度deque已滿,新增新項時,從另一端丟棄相應數量的項。

>>> from collections import deque
>>> 
>>> 
>>> my_deque = deque([1,2,3])
>>> 
>>> my_deque.append(4)
>>> 
>>> my_deque
deque([1, 2, 3, 4])
>>> my_deque.appendleft(0)
>>> 
>>> 
>>> my_deque
deque([0, 1, 2, 3, 4])
>>> my_deque.pop()
4
>>> 
>>> my_deque.popleft()
0
>>> 
>>> my_deque
deque([1, 2, 3])

deque擁有的方法

deque擁有眾多的方法,因為deque是一個雙向連結串列,所以在頭和尾都可以操作,方法有如下:
deque.append(x) 在佇列右邊(尾部)插入x
deque.appendleft(x) 在佇列左邊(頭部)插入x
deque.pop() 在佇列左邊(尾部)出棧
deque.popleft() 在佇列右邊(頭部)出棧
deque.clear() 清空佇列,初始化佇列長度為1
deque.copy() 建立一個淺拷貝的佇列
deque.count(x) 計算佇列中x元素的個數
deque.extend(iterable) 將可迭代物件擴充套件到佇列右邊
deque.extendleft(iterable)將可迭代物件擴充套件到佇列左邊
deque.index(x) 返回元素x在佇列中的位置,沒有找到丟擲異常ValueError
deque.insert(i, x) 插入元素x到指定位置x。如果位置超出範圍,丟擲IndexError異常
deque.remove(x) 刪除第一個找到的元素x,沒有元素丟擲ValueError異常
deque.reverse() 逆序
deque.rotate(n) 將佇列向右移動n個元素,如果n為負數則向左移動n個元素
deque.maxlen() 返回佇列長度,為空返回None。

namedtuple

命名元組類似於一個物件,包含只讀屬性的變數
功能:
命名元組為元組中的每個位置分配意義,並允許更可讀、自文件化的程式碼。它們可以在任何使用常規元組的地方使用,並且它們增加了通過名稱而不是位置索引訪問欄位的能力。
namedtuple建立一個和tuple類似的物件,而且物件可以通過名字訪問。這物件更像帶有資料屬性的類,不過資料屬性是隻讀的。

namedtuple(typename,field_names,*,verbose=False, rename=False, module=None)

1)typename:該引數指定所建立的命名元組的名字
2) field_names:該引數指定各個元素對應的名字,如 ['x','y'],也可以用"x,y"

簡單使用

有一個長方體,長寬高的屬性用一個元組來表示(12, 15, 16)。雖然三個數值能夠表示一個長方體,但是這個元組並沒有清楚的指明三個欄位分別代表什麼。其實三個欄位分別是:

12 15 16

使用namedtuple來表示就能夠很清晰的表示出來。

from collections import namedtuple

Cuboid = namedtuple("Cuboid", ["length", "width", "height"])
one = Cuboid(12, 15, 16)
>>> one[0]
12
>>> one.length
12
>>> 
>>> 
>>> one[1]
15
>>> one.width
15
>>> 
>>> 
>>> one[2]
16
>>> one.height
16
>>> 

可以看出namedtuple的方法方式有如下關係:
noe.length == one[0]
one.width == one[1]
one.height == one[2]

三個方法

除了繼承元組的方法,命名元組還支援三個額外的方法和兩個屬性
1._asdict()
返回一個新的 dict ,它將欄位名稱對映到它們對應的值

>>> one._asdict()
OrderedDict([('length', 12), ('width', 15), ('height', 16)])

2._replace

>>> two = one._replace(length=33)
>>> two.length
33

3._make
類方法,從存在的序列或迭代例項建立一個新例項。

>>> arr = [100,200,150]
>>> 
KeyboardInterrupt
>>> three = Cuboid._make(arr)
>>> three
Cuboid(length=100, width=200, height=150)
>>> three[0]
100
>>> three.length
100

兩個屬性

1._fields
出了欄位名

>>> one._fields
('length', 'width', 'height')

2.defaults
給欄位設定預設值

>>> Cuboid = namedtuple("Cuboid", ["length", "width", "height"], defaults=[22,33,44])
>>> four = Cuboid()
>>> four[0]
22
>>> four.length
22
>>> 
>>> 
>>> four[1]
33
>>> four.width
33

OrderedDict

有順序的字典和常規字典類似,但是有很多和排序有關的能力。但是有序字典現在變得不是那麼重要了,因為在python3.7開始常規字典也能記住插入的順序。
有序字典和常規字典仍然有一些不同之處:

  1. 常規字典被設計用於對映操作,保持插入順序是其次的。
  2. 有序字典被設計用於排序操作,高效空間使用,迭代速度和更新操作是其次的
  3. 在演算法上,有序字典處理頻繁排序操作比常規字典更好,這個特性讓有序字典可以適合追蹤最近訪問,如LRU
  4. OrderedDict 類的 popitem() 方法有不同的簽名。它接受一個可選引數來指定彈出哪個元素
  5. OrderedDict 類有一個 move_to_end() 方法,可以有效地將元素移動到任一端
傳統字典方法 OrderedDict方法 差異
clear clear
copy copy
fromkeys fromkeys
get get
items items
keys keys
pop pop
popitem popitem OrderedDict 類的 popitem() 方法有不同的簽名。它接受一個可選引數來指定彈出哪個元素。
setdefault setdefault
update update
values values
move_to_end 可以有效地將元素移動到任一端。

Python3.6之前的dict是無序的,但是在某些情形我們需要保持dict的有序性,這個時候可以使用OrderedDict.

>>> from collections import OrderedDict
>>> 
>>> ordict = OrderedDict()
>>> 
>>> ordict['x'] = 200
>>> ordict['y'] = 300
>>> ordict['z'] = 100
>>> ordict['a'] = 400
>>> 
>>> 
>>> ordict
OrderedDict([('x', 200), ('y', 300), ('z', 100), ('a', 400)])
>>> ordict.pop('z')
100
>>> ordict
OrderedDict([('x', 200), ('y', 300), ('a', 400)])

兩個特殊方法

1.popitem刪除頭或尾
dic.popitem(): 刪除尾元素
dic.popitem(False): 刪除頭元素

>>> ordict.popitem()
('a', 400)

>>> ordict.popitem(False)
('x', 200)

2.dic.move_to_end(): 將指定鍵值對移動到尾部

>>> ordict
OrderedDict([('y', 300), ('z', 100), ('a', 1), ('b', 2)])
>>> ordict.move_to_end('y')
>>> ordict
OrderedDict([('z', 100), ('a', 1), ('b', 2), ('y', 300)])

ChainMap

ChainMap 對映鏈
功能:
ChainMap提供了一種多個字典整合的方式,它沒有去合併這些字典,而是將這些字典放在一個列表(maps)裡,內部實現了很多 dict 的方法,大部分 dict 的方法,ChainMap 都能使用。

from collections import ChainMap
a = {'x':1,'y':2}
b = {'x':100, 'z':200}
c = ChainMap(a,b)
>>> c
ChainMap({'x': 1, 'y': 2}, {'x': 100, 'z': 200})
>>> c.get('x')        
1
>>> c.get('z')
200
>>> 

在儲存中類似於[a,b],即[{'x': 1, 'y': 2}, {'x': 100, 'z': 200}],這是ChainMap的特點,按照前後順序儲存每一個map

操作

1.讀取
對ChainMap的讀取會從第一個map開始讀,直到遇到指定的key,返回第一個遇到的元素

2.更新
對ChainMap的修改只會影響第一個元素

>>> c["x"] = 33
>>> 
>>> c
ChainMap({'x': 33, 'y': 2}, {'x': 100, 'z': 200})

擁有的方法

1.maps
一個可以更新的對映列表。這個列表是按照第一次搜尋到最後一次搜尋的順序組織的。它是僅有的儲存狀態,可以被修改。列表最少包含一個對映。
ChainMap內部儲存了一個名為maps的list用以維護mapping關係, 這個list可以直接檢視和修改,修改之後相應ChainMap值也就修改了.

>>> c.maps
[{'x': 1, 'y': 2}, {'x': 100, 'z': 200}]

2.new_child
new_child(m=None, **kwargs)
返回一個新的 ChainMap,其中包含一個新的對映,後面跟隨當前例項中的所有對映。 如果指定了 m,它會成為新的對映加在對映列表的前面;如果未指定,則會使用一個空字典,因此呼叫 d.new_child() 就等價於 ChainMap({}, *d.maps)。 如果指定了任何關鍵字引數,它們會更新所傳入的對映或新的空字典。 此方法被用於建立子上下文,它可在不改變任何上級對映的情況下被更新。

>>> c.new_child()
ChainMap({}, {'x': 33, 'y': 2}, {'x': 100, 'z': 200})

3.parents
屬性返回一個新的 ChainMap 包含所有的當前例項的對映,除了第一個。這樣可以在搜尋的時候跳過第一個對映。 使用的場景類似在 nested scopes 巢狀作用域中使用 nonlocal 關鍵詞。用例也可以類比內建函式 super() 。

一個 d.parents 的引用等價於 ChainMap(*d.maps[1:]) 。

>>> c.parents
ChainMap({'x': 100, 'z': 200})
>>> 

ChainMap只是對之前的字典做一個引用,因此,修改ChainMap會影響到之前的字典,同理修改原來的字典也會影響到ChainMap

c["x"] = 33
>>> c.new_child()
ChainMap({}, {'x': 33, 'y': 2}, {'x': 100, 'z': 200})
>>> a
{'x': 33, 'y': 2}
>>> 
>>> a['x'] = 44
>>> c
ChainMap({'x': 44, 'y': 2}, {'x': 100, 'z': 200})

總結

collections號稱是基本資料結構的增強版,如果基本資料結構是iphone13,那collections就是iphone13 pro,iphone13 pro max。可以將是否熟練使用collections作為python進階程式設計的一個衡量標準。

相關文章