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 を返してくれます。
2. 初期値を持つ辞書:defaultdict
Section titled “2. 初期値を持つ辞書:defaultdict”通常の辞書で、まだ存在しないキーに対して値を追加・更新しようとすると 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]) # 10print(p.x) # 10 (こちらの方が圧倒的に読みやすい)print(p.y) # 20タプル同様にイミュータブル(変更不可)であるため、辞書のキーとして使うことも可能です。