Skip to content

9.2 さまざまなコンテナデータ型を扱う — collections

Pythonには、標準で list(リスト)、dict(辞書)、tuple(タプル)、set(集合)といった強力なコンテナ(データを入れる箱)が備わっています。

しかし、特定の用途においては「もっと簡単に数え上げたい」「キーが存在しない時のエラーを防ぎたい」「両端から高速にデータを出し入れしたい」といった要求が出てきます。そんな時に大活躍するのが、標準ライブラリの collections モジュールです。

本記事では、「9.2 さまざまなコンテナデータ型を扱う」として、実務で頻出する4つの強力なデータ型を解説します。

1. 要素の出現回数をカウントする:Counter

Section titled “1. 要素の出現回数をカウントする:Counter”

リストや文字列の中に、どの要素が何回出現したかを数えたい場合、自分で for ループと辞書を使って書くのは面倒です。Counter を使えば、一瞬で集計が完了します。

from collections import Counter
# リストの要素をカウント
fruits = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
counts = Counter(fruits)
print(counts)
# 出力: Counter({'apple': 3, 'banana': 2, 'cherry': 1})
# 出現回数が多い順に取得する(上位2つ)
print(counts.most_common(2))
# 出力: [('apple', 3), ('banana', 2)]

Counter は辞書(dict)のサブクラスなので、counts['apple'] のようにキーを指定してアクセスすることも可能です。存在しない要素を指定しても KeyError にならず 0 を返してくれます。

通常の辞書で、まだ存在しないキーに対して値を追加・更新しようとすると KeyError が発生します。これを防ぐために if key in d: といった条件分岐を書くのはコードが長くなる原因です。

defaultdict は、存在しないキーにアクセスした際、あらかじめ指定した「初期値を生成する関数(ファクトリ関数)」を呼び出して自動的に初期値を作ってくれます。

リストを初期値にする例(データのグループ化に便利)

Section titled “リストを初期値にする例(データのグループ化に便利)”
from collections import defaultdict
# 存在しないキーが呼ばれたら、自動的に空のリスト [] を返す
grouped_data = defaultdict(list)
words = ['apple', 'bat', 'bar', 'atom', 'book']
# 頭文字ごとに単語をグループ化する
for word in words:
initial = word[0]
grouped_data[initial].append(word) # 初期化チェック不要!
print(dict(grouped_data))
# 出力: {'a': ['apple', 'atom'], 'b': ['bat', 'bar', 'book']}

数値の集計なら defaultdict(int) を指定すれば、初期値が 0 の辞書になります。

3. 両端から高速に出し入れできるキュー:deque(デック)

Section titled “3. 両端から高速に出し入れできるキュー:deque(デック)”

Pythonの標準リスト(list)は、末尾への追加(append)や末尾からの取り出し(pop)は高速ですが、先頭への追加や先頭からの取り出し(pop(0))は非常に遅いという弱点があります(要素を一つずつ後ろにずらす処理が発生するため)。

deque(Double-Ended Queue)は、両端からの追加・取り出しがどちらも超高速(O(1))で行えるデータ構造です。

from collections import deque
# dequeの作成
queue = deque(['A', 'B', 'C'])
# 右端(末尾)に追加 / 取り出し
queue.append('D')
print(queue.pop()) # 'D' を取り出す
# 左端(先頭)に追加 / 取り出し
queue.appendleft('Z')
print(queue) # deque(['Z', 'A', 'B', 'C'])
print(queue.popleft()) # 'Z' を取り出す

大量のデータを先頭から順に処理していく(FIFO: First In First Out)プログラムを書く場合は、必ず list ではなく deque を使用しましょう。

4. 名前付きフィールドを持つタプル:namedtuple

Section titled “4. 名前付きフィールドを持つタプル:namedtuple”

タプルは (x, y) のように複数の値をまとめるのに便利ですが、値を取り出す時に pt[0] のようにインデックス(数字)を使うため、後から見た時に「0番目が何を表しているか」が分かりにくいという欠点があります。

namedtuple を使うと、オブジェクトのプロパティのように「名前」で値にアクセスできるタプルを作成できます。クラスを定義するよりもずっと手軽です。

from collections import namedtuple
# 'Point' という名前の型で、'x' と 'y' というフィールドを持つ namedtuple を作成
Point = namedtuple('Point', ['x', 'y'])
# インスタンス化
p = Point(10, 20)
# インデックスでも、名前でもアクセス可能
print(p[0]) # 10
print(p.x) # 10 (こちらの方が圧倒的に読みやすい)
print(p.y) # 20

タプル同様にイミュータブル(変更不可)であるため、辞書のキーとして使うことも可能です。