本文 AI 產出,尚未審核

Python 課程 – 資料結構:字典(dict)之 defaultdictCounter


簡介

在 Python 中,dict 是最常用的資料結構之一,提供了「鍵 → 值」的映射關係。然而,當我們需要在字典裡自動產生預設值或進行計數時,原生的 dict 常會讓程式碼變得冗長且易錯。為了簡化這類需求,collections 模組提供了兩個非常實用的子類別:defaultdictCounter

  • defaultdict 讓你在存取不存在的鍵時,自動以指定的「預設工廠」產生一個初始值,省去手動檢查與 if 判斷的步驟。
  • Counter 則是專為「計數」設計的字典子類別,內建多種統計方法(如 most_common、加減運算等),在文字分析、日誌統計、購物車等情境中非常方便。

掌握這兩個工具,不僅能讓程式碼更簡潔、可讀,還能提升開發效率與錯誤防護能力。以下將深入說明它們的概念、使用方式與實務應用。


核心概念

1️⃣ defaultdict:自動產生預設值的字典

defaultdict 繼承自內建的 dict,唯一的差別在於它接受一個 default_factory(預設工廠)作為建構子參數。當存取一個不存在的鍵時,defaultdict 會呼叫 default_factory 產生一個新值,並自動插入字典。

常見的 default_factory

類型 說明 產生的預設值
list 用於收集多個元素 []
set 用於去除重複元素 set()
int 用於計數 0
float 用於浮點數累計 0.0
lambda: <value> 自訂任意預設值 <value>

範例 1:分組(groupby)資料

from collections import defaultdict

# 假設有一串座標資料,每個座標都有屬於哪個城市
points = [
    ("Taipei", (25.03, 121.56)),
    ("Kaohsiung", (22.63, 120.30)),
    ("Taipei", (25.04, 121.57)),
    ("Tainan", (22.99, 120.20)),
]

city_points = defaultdict(list)   # default_factory 為 list
for city, coord in points:
    city_points[city].append(coord)

print(city_points)
# Output: defaultdict(<class 'list'>, {'Taipei': [(25.03, 121.56), (25.04, 121.57)], 'Kaohsiung': [(22.63, 120.30)], 'Tainan': [(22.99, 120.20)]})

重點:不需要先檢查 city 是否已存在於字典,defaultdict 會自動建立空列表。

範例 2:統計字元出現次數(不使用 Counter

from collections import defaultdict

text = "Python程式設計教學"
char_cnt = defaultdict(int)   # default_factory 為 int,預設值 0
for ch in text:
    char_cnt[ch] += 1

print(char_cnt)
# Output: defaultdict(<class 'int'>, {'P': 1, 'y': 1, 't': 1, 'h': 1, 'o': 1, 'n': 1, '程': 1, '式': 1, '設': 1, '計': 1, '教': 1, '學': 1})

技巧:若只需要簡易計數,defaultdict(int) 已足夠,且執行速度與 Counter 相當。

範例 3:集合去重的分組

from collections import defaultdict

data = [
    ("apple", "red"),
    ("banana", "yellow"),
    ("apple", "green"),
    ("banana", "green"),
]

color_set = defaultdict(set)   # default_factory 為 set
for fruit, color in data:
    color_set[fruit].add(color)

print(color_set)
# Output: defaultdict(<class 'set'>, {'apple': {'red', 'green'}, 'banana': {'yellow', 'green'}})

說明:利用 set 作為預設值,自動去除同一水果的重複顏色。


2️⃣ Counter:專用於計數的字典

Counter 繼承自 dict,內部的值皆為整數,代表「某個項目出現的次數」。它提供了許多便利的方法,例如 most_commonelements、加減運算等。

範例 4:文字頻率統計

from collections import Counter

sentence = "Python 是一門簡潔且強大的程式語言。Python 讓開發者能快速完成任務。"
# 先把句子切成單字(簡化示範,實務上會使用 jieba 等斷詞工具)
words = [w for w in sentence if w.isalpha()]   # 只保留中文或英文字母
counter = Counter(words)

print(counter)
# Output: Counter({'P': 2, 'y': 2, 't': 2, 'h': 2, 'o': 2, 'n': 2, '是': 1, '一': 1, '門': 1, '簡': 1, '潔': 1, '且': 1, '強': 1, '大': 1, '的': 1, '程': 1, '式': 1, '語': 1, '言': 1, '讓': 1, '開': 1, '發': 1, '者': 1, '能': 1, '快': 1, '速': 1, '完': 1, '成': 1, '任': 1, '務': 1})

重點Counter 會自動把不存在的鍵視為 0,等同於 defaultdict(int),但提供更多統計功能。

範例 5:取得前 N 名高頻詞

top3 = counter.most_common(3)
print(top3)   # [('P', 2), ('y', 2), ('t', 2)]

說明most_common(n) 直接回傳前 n 個出現次數最高的項目,省去自行排序的麻煩。

範例 6:兩個 Counter 的加法與減法

c1 = Counter(a=3, b=1, c=2)
c2 = Counter(a=1, b=2, d=4)

print(c1 + c2)   # Counter({'a': 4, 'c': 2, 'b': 3, 'd': 4})
print(c1 - c2)   # Counter({'a': 2, 'c': 2})   # 只保留正值

技巧:使用 +-&(交集)與 |(聯集)可以快速完成集合運算,對於統計多來源資料特別有用。


常見陷阱與最佳實踐

陷阱 說明 解決方式
忘記傳入 default_factory defaultdict() 若未提供 default_factory,會變成普通 dict,存取不存在的鍵仍會拋 KeyError 務必在建構時明確指定(如 defaultdict(list))。
default_factory 回傳可變物件時的共享問題 若使用 default_factory=lambda: [],每次呼叫都會產生新列表;但如果使用 default_factory=list,行為相同。若自行寫函式返回同一個物件,可能會導致多個鍵共享同一個列表。 使用內建型別或 lambda 產生新實例,避免返回同一個可變物件。
Counter 負值的處理 減法後的負值會被保留在 Counter 內,但在大多數統計情境下不需要負值。 使用 c.subtract(other) 後,+& 重新正規化,或手動過濾 if v > 0
大型資料集的記憶體使用 defaultdictCounter 都會將每個鍵都保留下來,若鍵的種類非常多,可能會佔用大量記憶體。 使用 collections.defaultdict 搭配 itertools.islice外部儲存(如 SQLite、Redis)分批處理。
**most_common 產生的列表是 **快照 most_common 會返回一個全新列表,對原始 Counter 沒有影響。若需要即時更新,請直接操作 Counter 本身。 如需即時排序,可使用 sorted(counter.items(), key=lambda x: x[1], reverse=True)

最佳實踐

  1. 優先使用 defaultdictCounter 替代手動 if key in dict 判斷,可提升可讀性。
  2. 在需要多層嵌套的分組時,考慮使用 defaultdict(lambda: defaultdict(list)),簡化深層結構的建立。
  3. 對於計數需求,若只需簡單統計,Counter 是最直接的選擇;若需要自訂預設值或更複雜的資料結構,則使用 defaultdict
  4. 在多執行緒環境下defaultdictCounter 本身不是線程安全的,若共享寫入,請使用 threading.Lock 包裝。

實際應用場景

  1. 文字分析與自然語言處理

    • 使用 Counter 統計詞頻、字頻,快速找出關鍵詞或建立詞雲。
    • 結合 defaultdict(list) 分組同義詞或同類型詞彙。
  2. 日誌(log)統計

    • defaultdict(int) 逐行讀取伺服器日誌,統計每個狀態碼(200、404、500)出現次數。
    • Counter 可以直接累加多台機器的日誌結果,使用加法合併。
  3. 購物車與庫存管理

    • Counter 代表商品數量,支援加減操作,輕鬆處理加購、退貨。
    • defaultdict(set) 用於記錄每個客戶購買過的商品集合,避免重複推薦。
  4. 資料分組與聚合

    • 在 ETL 流程中,利用 defaultdict(list) 把同一類別的記錄聚集在一起,再一次性寫入資料庫。
    • defaultdict(lambda: defaultdict(int)) 可同時完成「類別 → 子類別 → 數量」的多層聚合。
  5. 圖形演算法

    • defaultdict(list) 建立鄰接表(adjacency list),方便 BFS、DFS 的實作。
    • Counter 追蹤圖中節點的訪問次數或邊的權重累計。

總結

defaultdictCounter 是 Python collections 模組中兩把「提效神器」:

  • defaultdict:讓字典在缺少鍵時自動產生預設值,特別適合分組、聚合、集合類型的操作。
  • Counter:專為計數設計,內建排序、加減與集合運算,讓文字統計、日誌分析等任務變得一鍵搞定。

掌握它們的使用方式、注意常見陷阱,並配合最佳實踐,就能在日常開發與資料處理工作中寫出更簡潔可讀高效的程式碼。希望這篇文章能幫助你在 Python 的字典世界裡,快速上手 defaultdictCounter,並在實務上發揮最大的威力!祝你編程順利 🚀。