iT邦幫忙

1

【python內建模組- collections】選擇容器的藝術

哈囉,大家好,
本篇要簡介的是python內建模組- collections
此模組實現了提供特定目標的容器。

其中最好用的功能大概是計數吧,
它可以讓你非常方便統計一個列表的元素出現了幾次,
依序來看看collections模組有些什麼東西吧

完整參考: Python官方文檔- collections

容器介紹一、namedtuple()-可命名屬性的元組

有時候,用一個列表或元組裡面裝的資料可能有些特別的涵義,
例如我們用可以用一個元組表示一個人的國、英、數三科學測的分數,
比如說(9, 6, 11)表示國文9級分,英文6級分,數學11級分

然而當我們開發大型程式時,
可能看到(9, 6, 11)具體並不記得這是什麼東西,
這時,使用namedtuple()便可以讓我們為每個分量做命名了。

範例如下:

from collections import namedtuple
Score = namedtuple('Score', ['Chinese', 'English', 'math'])
s1 = Score(9, 6, 11)
print(s1)
print(s1.Chinese)

容器介紹二、deque()- 高效的在列表兩端刪增元素

deque是個類似列表(list)的容器,
它實現了高效率的在容器的左、右兩端新增或刪除元素,
因此適合用來做為資料結構上的stackqueue來使用。
(若你不懂資料結構的話也沒關係,這並非本系列文討論的重點,暫時看過就好)
以下是deque()提供的幾個方法:

append(x)

添加x到右端

appendleft(x)

添加x到左端

pop(x)

移除並返回deque最右端的元素

popleft(x)

移除並返回deque最右端的元素

那list跟deque差在哪裡呢?
算是差在效能上的考量,
list在左端插入/刪除元素很慢,
但deque在左、右兩端插入/刪除元素都很快,
若有特殊需求的話可選擇deque來使用

list右插入 v.s. deque右插入

我們同樣在list和deque的右端插入一百萬個元素,
用time模組測量時間,程式如下:

from collections import deque
import time

# 測試list的插入效能
tStart = time.time()#計時開始
myList = []
for i in range(10**6):
    myList.append(i)
tEnd = time.time()#計時結束
print("Total time= %f seconds" % (tEnd - tStart))

# 測試deque的插入效能
tStart = time.time()#計時開始
myDeque = deque()
for i in range(10**6):
    myDeque.append(i)
tEnd = time.time()#計時結束
print("Total time= %f seconds" % (tEnd - tStart))

結果為:
Total time= 0.180000 seconds
Total time= 0.150000 seconds
你會覺得說感覺兩者之間好像也沒差多少嘛,
我們再比較左插入的狀況看看。

list左插入 v.s. deque左插入

這次我們在list和deque的左端插入十萬個元素,
一樣用time模組測量時間,程式如下:

from collections import deque
import time

# 測試list的插入效能
tStart = time.time()#計時開始
myList = []
for i in range(10**5):
    myList.insert(0,i) #在列表index 0的地方插入元素
tEnd = time.time()#計時結束
print("Total time= %f seconds" % (tEnd - tStart))

# 測試deque的插入效能
tStart = time.time()#計時開始
myDeque = deque()
for i in range(10**5):
    myDeque.appendleft(i)
tEnd = time.time()#計時結束
print("Total time= %f seconds" % (tEnd - tStart))

結果為:
Total time= 2.180000 seconds
Total time= 0.030000 seconds
哇,效率相差將近百倍呢,驚不驚人?
由此可知在適當的時機選擇適合的容器相當重要呢。

容器介紹三、defaultdict()- 有默認值的字典

(建議複習: 【python入門教室】(5) 內建型態介紹: 元組tuple(),字典dict(), 集合set()字典)
我們可能會想到說可以用一個字典(dict)來統計列表中每個元素的個數,
用key記錄元素內容,value記錄該元素出現的次數
譬如想這樣寫:

myList = ['A','B','A','A','B','B','A','C','C','C']
count = {} #{}表示一個空字典
for element in a_list:
    count[element] += 1

用for迴圈遍歷列表的每個元素,
然後把計數加一,
看起來蠻合理的,
但是直接執行程式的話會出現KeyError
因為字典的鍵值(key)必需要先初始化才能用。

因此,我們每次都要先檢查key值存不存在才行,
正確程式如下:

myList = ['A','B','A','A','B','B','A','C','C','C']
count = {} #{}表示一個空字典
for element in myList:
    if element not in count:
        count[element] = 1
    else:
        count[element] += 1
print(count)

結果為: {'A': 4, 'B': 3, 'C': 3}
但是這樣又有點失去python的簡潔性了。
能不能我們把沒用過的key值都設一個默認值(譬如:0),
這樣只要每次都把count[element]加一就好,
不用先去判斷key值是否存在。

這時便可以用defaultdict()-可以默認值的字典來幫忙了,
defaultdict裡面需傳入一個匿名函數回傳默認值。

程式修改如下:

from collections import defaultdict
myList = ['A','B','A','A','B','B','A','C','C','C']
count = defaultdict(lambda: 0) #預設字典若key值不存在的value為0
for element in myList:
    count[element] += 1
print(count)
print(count['A']) #查詢'A'出現幾次
print(count['D']) #查詢'D'出現幾次

結果為:
defaultdict(<function <lambda> at 0x000000000B8E99D8>, {'A': 4, 'B': 3, 'C': 3})
4
0

注意這邊我們是可以直接查詢count['D']的值的,
但是如果是python內建的字典則不行,
一定要key值存在才可以查,
這算是defaultdict方便的地方吧

容器介紹四、Counter()- 計數小幫手

說到計數功能的話,還是使用Counter最直接了。
Counter也可以把它想成是字典的一種,
不過跟defaultdict比起來,
Counter多了一些專為計數而設的功能,
因此若是要統計個數的話Counterdefaultdict好用許多

範例程式如下:

from collections import Counter
myList = ['A','B','A','A','B','B','A','C','C','C']
count = Counter(myList)
print(count)
print(count['A'])
print(count['D']) #對於不存在的值默認為0
print(count.most_common(2)) #找出出現次數最高的兩個元素

結果為:
Counter({'A': 4, 'B': 3, 'C': 3})
4
0
[('A', 4), ('B', 3)]
今天就介紹到這邊啦,
在課後練習中,
小馬將引導讀者學習統計一篇英文文章每個字出現的個數。

課後練習 - 統計每個單字的出現次數

現在我們拿到一篇英文的文章,
想要統計說每個單字出現了幾次。
比如說底下是小馬隨意打的英文句字:

article = "How are you? I am fine, thank you. And you you you you you? You are so smart."

像這一類跟「計數」相關的問題,
就很適合用Counter來解決。

不夠既然是統計單字出現次數嘛,
我們並不太在意大小寫的區別,
譬如說Youyou都應該視為you來統計

我們也會想忽略標點符號,
總不希望統計的結果把you.you?各自都算了一遍

小馬幫你把統計英文單字的架構打出來:

def trans(article):
    pass #現在這支函數什麼都沒做,等你完成

article = "How are you? I am fine, thank you. And you you you you you? You are so smart."
article = trans(article) #將字串全部變成小寫,並去除標點符號
print(article) #測試印出轉換後的字串
count = Counter(article.split())
print(count)  #印出每個字出現的次數

其中,我們定義一個轉換函數trans(article)
效果是把article的字母全部變成小寫,
然後把不是英文字母的字(如標點符號)替換成空白,
函數回傳一個字串。
預期結果:

how are you  i am fine  thank you  and you you you you you  you are so smart 
Counter({'you': 8, 'are': 2, 'how': 1, 'i': 1, 'am': 1, 'fine': 1, 'thank': 1, 'and': 1, 'so': 1, 'smart': 1})

你的練習是將trans(article)這支函數做好,
使得程式能夠印出正確的結果,
祝練習愉快~






參考答案

習題- 統計每個單字出現的次數

def trans(article):
    return ''.join([c.lower() if c.isalpha() else ' ' for c in article])

article = "How are you? I am fine, thank you. And you you you you you? You are so smart."
article = trans(article) #將字串全部變成小寫,並去除標點符號
print(article) #測試印出轉換後的字串
count = Counter(article.split())
print(count)  #印出每個字出現的次數

尚未有邦友留言

立即登入留言