【Python】collectionsの使い方|Counter・defaultdict・namedtuple・deque

【Python】collectionsの使い方|Counter・defaultdict・namedtuple・deque Python

Pythonの標準モジュールcollectionsには、辞書リストをもっと便利にしたデータ構造がそろっています。「要素の数を数える」「キーが無くてもエラーにしない」「両端への追加・削除を速くする」といった、よくある処理を短く・効率的に書けます。

とくに使う場面が多いのが、要素を数えるCounterキーが無くてもエラーにならないdefaultdict名前付きタプルのnamedtuple両端の操作が速いdequeの4つです。どれも自分で書くと少し面倒な処理を、1行で済ませてくれます。この記事では、実機のPythonで確認しながら、これらの使い方を整理します。

先に結論

  • Counter:要素の出現回数を数えます。most_common()で頻出順に取れます。
  • defaultdict:存在しないキーにアクセスしてもKeyErrorにならず、初期値が使われます。
  • defaultdict(list)を使うと、グルーピングが簡潔に書けます。
  • namedtuple:タプルの各要素に名前を付けて、p.xのように読めます。
  • deque両端への追加・削除が高速。キューやスタックに向いています。
  • いずれもfrom collections import ...で読み込みます。

土台となる辞書(dict)リスト(list)、名前付きタプルに関わるタプルと集合もあわせて参考になります。

スポンサーリンク

Counter:要素を数える

Counterは、リストや文字列の中で各要素が何回出てくるかを数えます。自分で辞書を使って数える処理が、たった1行で書けます。

Counter で数える
from collections import Counter

# 文字列の各文字を数える
c = Counter("banana")
print(c)          # Counter({'a': 3, 'n': 2, 'b': 1})
print(c["a"])     # 3

# リストの集計にも使える
words = ["りんご", "みかん", "りんご", "ぶどう", "りんご"]
print(Counter(words))   # Counter({'りんご': 3, 'みかん': 1, 'ぶどう': 1})

実機でも、Counter("banana"){'b': 1, 'a': 3, 'n': 2}と各文字の出現回数を数えました。リストを渡せば、各要素が何回出てきたかを集計できます。c["a"]のように辞書と同じ感覚で回数を取り出せます。アンケートの集計やログの集計など、「何が何回」を数える場面で大活躍します。

most_commonとCounter演算

Counterの便利なメソッドがmost_common()です。出現回数の多い順に取り出せます。また、Counterどうしは足し算・引き算もできます。

most_common と演算
from collections import Counter

c = Counter("banana")

# 出現回数の多い順(上位2件)
print(c.most_common(2))   # [('a', 3), ('n', 2)]

# Counter どうしの足し算(回数が合算される)
print(Counter("aab") + Counter("abc"))
#  → Counter({'a': 3, 'b': 2, 'c': 1})

実機でも、most_common(2)[('a', 3), ('n', 2)]と頻出上位2件を返しました。引数を省略すると全件を多い順で取得できます。さらに、Counter("aab") + Counter("abc")は回数を合算して{'a': 3, 'b': 2, 'c': 1}になりました。「ランキングを作る」「複数の集計結果を合算する」といった処理が、これだけで書けます。

defaultdict:KeyErrorを避ける

通常の辞書では、存在しないキーにアクセスするとKeyErrorになります。defaultdictを使うと、キーが無いときに自動で初期値を用意してくれるため、エラーを気にせず書けます。

defaultdict でKeyErrorを防ぐ
from collections import defaultdict

# NG: 通常の辞書は、存在しないキーに += するとエラー
counts = {}
# counts["a"] += 1   # KeyError: 'a'

# OK: defaultdict(int) なら初期値0から始まる
counts = defaultdict(int)
for ch in "banana":
    counts[ch] += 1    # 初回もエラーにならず0からカウント
print(dict(counts))    # {'b': 1, 'a': 3, 'n': 2}
初期値を自動で用意してくれる

実機で確認したところ、通常の辞書ではcounts["a"] += 1を最初に実行するとKeyErrorになりましたが、defaultdict(int)では存在しないキーでも初期値0から始まり、エラーなく数えられました。defaultdict(int)は「キーが無ければ0」、defaultdict(list)は「キーが無ければ空リスト[]」を自動で用意します。引数に渡すのはintlistといった初期値を作る関数です。これにより、「キーが存在するか毎回チェックする」という面倒なコードが不要になります。集計やグルーピングのコードが、ぐっと短く読みやすくなります。なお、dict(counts)のように通常の辞書へ変換すれば、普通の辞書として扱えます。

defaultdict(list)でグルーピング

defaultdict(list)のよくある使い方がグルーピング(仲間分け)です。「頭文字ごとに単語を分ける」「カテゴリごとにまとめる」といった処理が簡潔に書けます。

頭文字でグルーピング
from collections import defaultdict

words = ["apple", "ant", "banana", "bear"]

groups = defaultdict(list)
for word in words:
    groups[word[0]].append(word)   # 頭文字をキーにリストへ追加

print(dict(groups))
# {'a': ['apple', 'ant'], 'b': ['banana', 'bear']}

実機でも、頭文字をキーにして{'a': ['apple', 'ant'], 'b': ['banana', 'bear']}とグルーピングできました。defaultdict(list)なら、初めて出てくるキーでも自動で空リストが用意されるため、appendするだけで済みます。通常の辞書だと「キーが無ければ空リストを作る」という処理を毎回書く必要があり、defaultdictはそれを省けます。データの仕分けで頻出のパターンです。

namedtuple:名前付きタプル

namedtupleは、タプルの各要素に名前を付けられる仕組みです。p[0]のような番号ではなくp.xと名前で読めるため、コードが分かりやすくなります。

namedtuple で読みやすく
from collections import namedtuple

# フィールド名を指定して型を作る
Point = namedtuple("Point", ["x", "y"])

p = Point(10, 20)
print(p.x, p.y)    # 10 20(名前でアクセス)
print(p[0])        # 10(番号でもアクセスできる)

# タプルなのでアンパックもできる
x, y = p
print(x, y)        # 10 20

実機でも、Point(10, 20)の各要素にp.xp.yと名前でアクセスでき、p[0]のような番号アクセスやアンパックも通常のタプルと同じように使えました。ただのタプル(10, 20)では[0]が何を表すか分かりにくいですが、namedtupleならp.xで意図が明確です。座標・RGB色・データの1行など、意味のある複数の値をまとめるのに向いています。値を変更できない(イミュータブル)点はタプルと同じです。

deque:両端の高速操作

deque(デック)は、両端への追加・削除が速いリストのようなものです。通常のリストは先頭への挿入・削除(insert(0, ...)pop(0))が遅いのですが、dequeはそれが高速です。キュー(先入れ先出し)に向いています。

deque で両端を操作
from collections import deque

dq = deque([1, 2, 3])

dq.append(4)        # 右端に追加 → [1, 2, 3, 4]
dq.appendleft(0)    # 左端に追加 → [0, 1, 2, 3, 4]
print(list(dq))     # [0, 1, 2, 3, 4]

print(dq.popleft()) # 左端を取り出す → 0(速い)
print(dq.pop())     # 右端を取り出す → 4
キューには list より deque

実機でも、dequeappendleft(左端追加)・popleft(左端取り出し)で両端をスムーズに操作できました。通常のリストでもpop(0)で先頭を取り出せますが、リストの先頭操作は、要素数が増えるほど遅くなります(後ろの要素をすべてずらすため)。一方、dequeは両端の操作がどちらも高速です。「先に入れたものから順に処理する」キューや、幅優先探索などのアルゴリズムでは、dequeを使うのが定番です。逆に、真ん中の要素にインデックスで頻繁にアクセスする場合は、通常のリストのほうが向いています。用途に応じて使い分けてください。

主なクラスまとめ

collectionsのよく使うクラスをまとめます。

クラス 用途
Counter 要素の出現回数を数える・頻出順
defaultdict KeyErrorを避ける・グルーピング
namedtuple 名前付きのタプル(読みやすい)
deque 両端の高速な追加・削除(キュー)

よくある失敗

手作業で出現回数を数える

自分で辞書を使って数えるより、Counterが簡潔で確実です。

存在しないキーでKeyErrorになる

集計やグルーピングはdefaultdictを使うとエラーを避けられます。

defaultdictの引数に値を渡す

渡すのはintlistといった「関数」です。defaultdict(0)ではなくdefaultdict(int)です。

キューにlist.pop(0)を使う

要素が多いと遅くなります。両端操作はdequeを使います。

importを忘れる

from collections import Counterのように読み込みます。

よくある質問

Qリストの要素の出現回数を数えるには?
Afrom collections import CounterとしてCounter(リスト)を使います。各要素が何回出てくるかを数えた結果が得られ、most_common()で出現回数の多い順に取り出せます。自分で辞書を使って数えるより簡潔で確実です。
Qdefaultdictと通常の辞書の違いは?
A通常の辞書は存在しないキーにアクセスするとKeyErrorになりますが、defaultdictはキーが無いときに自動で初期値を用意します。defaultdict(int)なら0、defaultdict(list)なら空リストです。集計やグルーピングで、キーの存在チェックを省けます。
Qdefaultdict(int)とdefaultdict(0)はどちらが正しいですか?
Adefaultdict(int)が正しいです。引数には初期値そのものではなく、初期値を作る関数を渡します。int()は0、list()は空リストを返すため、defaultdict(int)defaultdict(list)と書きます。
Qnamedtupleは普通のタプルと何が違いますか?
A各要素に名前を付けられる点が違います。p[0]のような番号ではなくp.xと名前でアクセスでき、コードの意味が分かりやすくなります。タプルの性質(番号アクセス・アンパック・変更不可)はそのまま使えます。
Qキューを作るときはlistとdequeのどちらを使うべき?
Adequeを使います。通常のリストは先頭の取り出し(pop(0))が、要素が増えるほど遅くなります。dequeは両端の追加・削除がどちらも高速なため、先入れ先出しのキューや幅優先探索などに向いています。

まとめ

  • Counter:出現回数を数え、most_common()で頻出順に取得します。
  • defaultdict:KeyErrorを避け、defaultdict(list)でグルーピングが簡潔に。
  • namedtuple:タプルに名前を付けてp.xと読みやすく。
  • deque:両端の追加・削除が高速。キューに向いています。
  • いずれもfrom collections import ...で使えます。

collectionsのクラスは、辞書やリストで「少し面倒だな」と感じる処理を、短く・効率的に書くための道具です。とくにCounterdefaultdictは集計の場面で頻繁に使うので、まずこの2つから取り入れてみてください。コードがぐっと簡潔になり、バグも減らせます。