Q1リストnumsの要素ごとの出現回数を1 行で取得したい場合、最も簡潔なのはどれですか?
collections — Counter / defaultdict / deque / namedtuple
Counterの出現回数集計、defaultdictの欠損キー対策、dequeの両端O(1)操作とリングバッファ、namedtupleの名前付きレコードを実例で学べます。
collectionsモジュールには、list / dict / tuple の使いにくいところを補う特殊なデータ構造が集まっています。本記事では実プロジェクトでよく使うCounter / defaultdict / deque / namedtupleの 4 つを順に整理します。
4 つのデータ構造の役割分担
それぞれが「素の組み込み型では書きにくい場面」を簡潔にするために使用します。下の表で全体像を掴んでから、各セクションで具体的な使い方を見ていきます。
list / dict / tupleの上に使いやすい層を被せた拡張型。| 型 | 解決する問題 | 代わりに使えるもの |
|---|---|---|
| Counter | リストの要素を 1 つずつ数えるループが冗長 | for 文 + dict での集計 |
| defaultdict | 新しいキーを使う前に if x not in d: d[x] = ... が必要 | dict.setdefault(...) |
| deque | list.insert(0, ...) や list.pop(0) が遅い (O(n)) | list(小さいデータなら問題なし) |
| namedtuple | タプル要素の意味が位置でしか分からない | dataclass や dict(重め) |
Counter — 出現回数を 1 行で集計する
Counterはイテラブル(リスト・文字列など)を渡すと、各要素の出現回数を辞書として返してくれるクラスです。普通の dict を使うと「キーが既にあるか確認して、無ければ 1 で初期化、あれば +1」というループを書く必要がありますが、Counter ならCounter(リスト)の 1 行で同じことができます。
戻り値はdict のサブクラスなので、counter["apple"]のように普通の dict と同じ感覚で値を取り出せます。さらに.most_common(N)で「上位 N 件のタプルのリスト」を返してくれて、ランキング表示に便利です。
most_common(N)で頻度順の上位 N 件を取り出せるので、ランキング表示が 1 行で書ける。defaultdict — 欠損キーを自動で初期化する
defaultdictは「キーが存在しないときに自動で初期値を入れてくれる」辞書です。普通の dict だと、新しいキーに対してif key not in d: d[key] = []のような存在チェック → 初期化を毎回書く必要がありますが、defaultdict は作成時に「初期値を作る関数」を渡しておくだけで、欠損キーへのアクセス時に自動でその関数を呼んで初期値を作ってくれます。
よく使うのはdefaultdict(list)(欠損キーには空リスト)とdefaultdict(int)(欠損キーには 0)の 2 パターンです。引数は「呼び出すと初期値を返す関数」で、listもintも引数なしで呼ぶと空リスト / 0 を返すので、そのまま渡せます。
listを渡せば空リスト、intを渡せば0、setを渡せば空集合が初期値になる。Counter との使い分け
「単純に数を数えるだけ」ならCounterのほうが簡潔(most_commonも付いてくる)。「グループ化してリストや集合を作る」など、初期値が空コンテナの場合はdefaultdict(list) / defaultdict(set)が向きます。
deque — 両端の追加・取り出しが O(1)
deque(double-ended queue、デック)は両端から要素を追加・取り出しできる、リスト風のデータ構造です。普通のlistもappendとpopで右端の操作はできますが、list.insert(0, x) や list.pop(0) のように左端を操作すると、内部で全要素を 1 つずつシフトするためO(n)(要素数が増えれば遅く)なります。
dequeはこの両端の操作が O(1)(要素数によらず一定時間)で済む設計で、キュー / 履歴バッファ / 直近 N 件の保持など左右両方から出し入れする処理で重宝します。特に便利なのがmaxlen 引数で、最大長を指定すると、それを超える追加で自動的に反対側の要素が落ちるリングバッファとして動きます。
deque(maxlen=N)で古いものから自動的に落ちるリングバッファとして使える。| メソッド | 効果 | コスト |
|---|---|---|
| append(x) | 右端に追加 | O(1) |
| appendleft(x) | 左端に追加 | O(1) |
| pop() | 右端から取り出し | O(1) |
| popleft() | 左端から取り出し | O(1) |
| maxlen=N | 最大長を指定(リングバッファ風) | 超えると反対端が自動で落ちる |
namedtuple — タプルに名前を付ける軽量レコード
namedtupleは「フィールドに名前を付けたタプル」を 1 行で定義できる関数です。普通のタプル(3, 4)は位置でしかアクセスできない(p[0] / p[1])ので、p[0]が x なのか y なのかをコードを読む側が覚えておく必要があります。namedtuple ならp.x / p.y で意味のある名前で取り出せるので、可読性が大きく上がります。
さらにタプルとしての互換性も保たれているので、p[0]の位置アクセスや*pでのアンパックも普通のタプルと同じように動きます — 既存のコードを壊さずに「名前」だけを足せる、軽量な拡張です。
_asdict()で辞書にも変換できるので、JSON 化や pprint との相性も良い。dataclass との使い分け
変更不可の軽量レコードが欲しいだけならnamedtuple、メソッドやデフォルト値・型ヒントを細かく書きたいならdataclass(次々回扱います)。namedtuple はタプルとしても振る舞う互換性が強みで、def f() -> tuple[int, int]:のような既存の関数戻り値の置き換えに使いやすいです。
namedtuple の追加機能 — repr / インデックス / _asdict
namedtuple はクラスとしての名前アクセスに加えて、タプル互換のインデックスアクセス、読みやすい repr、辞書化メソッドという 3 つの追加機能を持っています。これらはデバッグ・既存 API との互換・JSON 化などで用います。
理解度チェック
まずは1問ずつ答えてみましょう。
Q2defaultdict(list)を作った後、初めて使うキー d["x"]の値はどうなりますか?
Q3直近 5 件だけ保持したいときに最も向いているのはどれですか?