0%

讓我們用collections中的Counter來計算數量

Python的collection模組裡面其實包含了許多非常實用的資料結構,比如之前介紹過的
namedtuple。今天要談的是Counter,Counter是一個dict的子類別,用來對hashable的物件作計算。

比如說我們今天要來幫公司裡面每個不同的team訂飲料好了,以下簡易一點不接受客製化調味,在建立Counter可以有以下幾種方法

1
2
3
4
5
6
7
8
9
10
# 使用mapping建立Counter,輸入一個dict
>>> team1 = Counter({'BlackTea': 3, 'GreenTea': 2, 'MilkTea': 1})
# 使用iterable建立Counter,輸入一個list
>>> team2 = Counter(['BlackTea', 'BlackTea', 'BlackTea', 'MilkTea', 'MilkTea', 'MilkTea'])
# 使用keyword參數建立Counter,輸入key-value組合
>>> team3 = Counter(GreenTea=3, MilkTea=3)
>>> team1['BlackTea'] # 可以直接當作dict存取
3
>>> team2['GreanTea'] # 取出不存在的item即為0
0

可以看到透過Couter能透過三種不同的方法來建立,就看使用的情境比較適合哪一種。而Counter也可以直接就當作dict取出值,特別是如果取出不存有的item,其計數會為0,並不會出現dict的KeyError。

1
2
3
4
5
6
7
8
>>> team1 + team2  # 使用加法運算符
Counter({'BlackTea': 6, 'MilkTea': 4, 'GreenTea': 2})
>>> team2 - team3 # 使用減法運算符
Counter({'BlackTea': 3})
>>> team2 & team3 # 運算兩者的交集
Counter({'MilkTea': 3})
>>> team2 - team3 # 運算兩者的聯集
Counter({'MilkTea': 3, 'GreenTea': 3, 'BlackTea': 3})

Counter也可以支援不同的運算符來對Counter物件進行操作,來達成對不同集合元件的運算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> drink_order = team1 + team2 + team3
Counter({'MilkTea': 7, 'BlackTea': 6, 'GreenTea': 5})

>>> list(drink_order) # 將Counter的items轉成list
['BlackTea', 'GreenTea', 'MilkTea']
>>> set(drink_order) # 將Counter的items轉成set
['BlackTea', 'GreenTea', 'MilkTea']
>>> dict(drink_order) # 將Counter的items轉成一般dict
{'BlackTea': 6, 'GreenTea': 5, 'MilkTea': 7}

>>> drink_order.items() # 取出item pairs
dict_items([('MilkTea', 7), ('BlackTea', 6), ('GreenTea', 5)])
>>> drink_order.keys() # 取出key值
dict_keys(['MilkTea', 'BlackTea', 'GreenTea'])
>>> drink_order.values() # 取出value值
dict_values([7, 6, 5])

>>> sum(drink_order.values()) # 取出value值並加總得到全部數量
18

因為Counter是dict的一個字類別,所以基本上他可以辦到原本dict可以作到的資料轉型,包含透過items()來逐一取出每個pair,或是直接可以使用values()並加總來計算出Counter裡面總計有多少數量的東西。

1
2
3
4
5
6
list(drink_order.elements())
>>> ['BlackTea', 'BlackTea', 'BlackTea', 'BlackTea', 'BlackTea', 'BlackTea', 'GreenTea', 'GreenTea', 'GreenTea', 'GreenTea', 'GreenTea', 'MilkTea', 'MilkTea', 'MilkTea', 'MilkTea', 'MilkTea', 'MilkTea', 'MilkTea']
>>> drink_order.most_common(3)
[('MilkTea', 7), ('BlackTea', 6), ('GreenTea', 5)]
>>> drink_order.most_common(2)
[('MilkTea', 7), ('BlackTea', 6)]

Counter還支援兩個函式,elements可以幫你展開所有的items。而most_common則會使用數量作排序,並且可以指定要排出前幾名。

Counter其實使用上非常方便,讓我們看一下Counter在實戰上可以拿來解什麼樣的問題,以下選兩個LeetCode的題目來看看Counter可以怎麼樣被使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
'''389. Find the Difference
Given two strings s and t which consist of only lowercase letters.
String t is generated by random shuffling string s and then add one more letter
at a random position. Find the letter that was added in t.
Input:
s = "abcd"
t = "abcde"
Output: e ('e' is the letter that was added.)
'''
def findTheDifference(self, s, t):
from collections import Counter
s_counter = Counter(s)
t_counter = Counter(t)
return list(t_counter - s_counter)[0]

LeetCode-389是給定兩個字串s和t,其中要判斷t比s多了哪個字元。這時候就可以直接將s和t分別以iterable的方式建立兩個Counter,而我們知道t會比s多一個字元,於是再透過減法來找到t和s的差集後,輸出是哪個item即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
'''819. Most Common Word
Given a paragraph and a list of banned words, return the most frequent word
that is not in the list of banned words. It is guaranteed there is at least
one word that isn't banned, and that the answer is unique.
Words in the list of banned words are given in lowercase, and free of punctuation.
Words in the paragraph are not case sensitive. The answer is in lowercase.
Example:
Input:
paragraph = "Bob hit a ball, the hit BALL flew far after it was hit."
banned = ["hit"]
Output: "ball"
'''
def mostCommonWord(self, paragraph, banned):
import re
from collections import Counter
words = re.sub("[!|?|'|,|;|.]", '', paragraph).lower().split(' ')
return (Counter(word for word in words if word not in banned).most_common(1)[0][0])

LeetCode-819是給定一個段落,還有被禁掉的詞清單,接著輸出出現字詞次數最多次,而且沒有被禁掉的詞。這邊先將所有詞轉成小寫,接著再透過空白切分後,將特殊符號取代掉只留下字詞。再來就可以將不存在於禁用清單的詞拿來建立Counter,然後使用most_common取出次數最高的第一名,接著再輸出是哪個詞即可。

Counter其實在計算次數的時候非常實用,特別是在項目很多的情況下,而且還需要作加減運算時,可以嘗試使用Counter來解問題,絕對可以幫助你省下很多的時間的!

參考資料:
Python docs - Counter dict subclass for counting hashable objects