Python: contare e raggruppare con `Counter` e `defaultdict`
Contare quante volte compare un valore o raggruppare elementi per chiave sono operazioni di ogni giorno. Il modulo collections le rende più corte, e di solito anche un po' più veloci.
Contare le occorrenze
from collections import Counter, defaultdict
parole = ["a", "b", "a", "c", "a"]
# a mano
d = {}
for p in parole:
if p in d:
d[p] += 1
else:
d[p] = 1
# con get()
d = {}
for p in parole:
d[p] = d.get(p, 0) + 1
# con defaultdict
d = defaultdict(int)
for p in parole:
d[p] += 1
# con Counter
d = Counter(parole)
d.most_common(2) # [('a', 3), ('b', 1)]
Misure (Python 3.12.15, migliore di 5 serie, 200.000 parole con 1.000 valori distinti):
if p in da mano: circa 10,2 msd.get(p, 0) + 1: circa 8,6 msdefaultdict(int): circa 9,2 msCounter(parole): circa 6,8 ms
Le quattro versioni danno lo stesso risultato. Il guadagno di Counter viene dal ciclo di conteggio scritto in C; la vera ragione per usarlo però è most_common(), che altrimenti dovresti scrivere con sorted.
Raggruppare in liste
coppie = [("k1", 1), ("k2", 2), ("k1", 3)]
d = defaultdict(list)
for chiave, valore in coppie:
d[chiave].append(valore)
# {'k1': [1, 3], 'k2': [2]}
Su 200.000 coppie e 500 chiavi:
- controllo
if k not in da mano: circa 10,3 ms d.setdefault(k, []).append(v): circa 10,3 msdefaultdict(list): circa 7,8 ms
setdefault crea una lista nuova a ogni chiamata, anche quando la chiave esiste già, e la butta via: per questo non è più veloce del controllo a mano. defaultdict(list) la crea solo se manca.
La trappola
Con defaultdict anche una semplice lettura crea la chiave:
d = defaultdict(int)
d["x"] # solo lettura...
"x" in d # True
Se controlli l'esistenza con d[k] invece di k in d, il dizionario si riempie di chiavi vuote. Per sola lettura usa k in d o d.get(k). Quando hai finito di costruirlo, dict(d) lo riporta a un dizionario normale che non crea chiavi.
Quale scegliere
- Contare e voler sapere i più frequenti:
Counter. - Raggruppare in liste o insiemi:
defaultdict(list)odefaultdict(set). - Pochi elementi o codice che deve restare banale: il dizionario normale va benissimo, la differenza è di pochi millisecondi su centinaia di migliaia di elementi.
Lo script con le misure è nel repository del sito (verifica/counter_defaultdict.py); i numeri variano con macchina e versione di Python.