9.6 イテレーターの組み合わせで処理を組み立てる — itertools
Pythonでリストやデータ群を処理する際、何重にもネストされた for ループを書いたり、膨大な組み合わせのリストをメモリ上に作成してシステムが重くなってしまった経験はありませんか?
標準ライブラリの 「itertools」 モジュールは、効率的なループ処理(イテレーション)を構築するための強力なツール群です。 最大のメリットは、すべてのデータを一度にリストとしてメモリに保持するのではなく、1つずつ要素を生成する「遅延評価(ジェネレーターと同じ仕組み)」を利用している点です。これにより、メモリ消費を最小限に抑えつつ、C言語レベルの高速な処理が可能になります。
本記事では、「9.6 イテレーターの組み合わせで処理を組み立てる」として、実務でよく使われる強力な関数群を3つのカテゴリーに分けて解説します。
1. 組み合わせと順列(Combinatorics)
Section titled “1. 組み合わせと順列(Combinatorics)”複数ネストした for ループを1行でスッキリ書いたり、数学的な順列・組み合わせを生成したりする関数群です。
1.1 直積(デカルト積):product()
Section titled “1.1 直積(デカルト積):product()”複数のリストから要素を1つずつ取り出した「すべての組み合わせ」を生成します。ネストされた for ループの代わりとして非常に有用です。
import itertools
colors = ['赤', '青']sizes = ['S', 'M', 'L']
# 従来の2重forループ# for c in colors:# for s in sizes:# print(c, s)
# productを使うと1つのループで書けるfor c, s in itertools.product(colors, sizes): print(f"{c}-{s}")
# 出力: 赤-S, 赤-M, 赤-L, 青-S, 青-M, 青-L1.2 順列(並べる順番を区別する):permutations()
Section titled “1.2 順列(並べる順番を区別する):permutations()”指定したデータ群から、指定した数だけ要素を取り出して並べる「すべての順列」を生成します。(A-B と B-A は別のものとして扱われます)
import itertools
items = ['A', 'B', 'C']# 3つの要素から2つを取り出して並べるfor p in itertools.permutations(items, 2): print(p)
# 出力: ('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')1.3 組み合わせ(並べる順番を区別しない):combinations()
Section titled “1.3 組み合わせ(並べる順番を区別しない):combinations()”指定したデータ群から、指定した数だけ要素を取り出す「すべての組み合わせ」を生成します。(A-B と B-A は同じものとみなされ、重複しません)
import itertools
items = ['A', 'B', 'C']# 3つの要素から2つを選ぶ組み合わせfor c in itertools.combinations(items, 2): print(c)
# 出力: ('A', 'B'), ('A', 'C'), ('B', 'C')2. 複数のイテレーターを繋ぐ・操作する
Section titled “2. 複数のイテレーターを繋ぐ・操作する”既存のリストやイテレーターを加工して、新しいループ処理を作る関数群です。
2.1 リストを連結する:chain()
Section titled “2.1 リストを連結する:chain()”複数のリストやイテレーターを、あたかも1つの連続したイテレーターであるかのように連結します。list1 + list2 のように新しいリストを作らないため、メモリを消費しません。
import itertools
list1 = [1, 2, 3]list2 = ['A', 'B', 'C']
for item in itertools.chain(list1, list2): print(item, end=' ')
# 出力: 1 2 3 A B C2.2 連続する要素をグループ化する:groupby()
Section titled “2.2 連続する要素をグループ化する:groupby()”連続している同じ値を持つ要素をグループ化します。(SQLの GROUP BY に似ていますが、「連続している要素のみ」をまとめる点に注意が必要です。全体をグループ化したい場合は、事前にソート sorted() しておく必要があります)
import itertools
# 事前に連続するようにソートされているデータdata = ['A', 'A', 'B', 'B', 'B', 'C']
for key, group in itertools.groupby(data): print(f"キー: {key}, 要素: {list(group)}")
# 出力:# キー: A, 要素: ['A', 'A']# キー: B, 要素: ['B', 'B', 'B']# キー: C, 要素: ['C']3. 無限に続くイテレーター(Infinite iterators)
Section titled “3. 無限に続くイテレーター(Infinite iterators)”使い方を間違えると無限ループ(フリーズ)に陥りますが、zip() 関数などと組み合わせて使うと非常に便利な関数群です。
3.1 無限にカウントアップ:count()
Section titled “3.1 無限にカウントアップ:count()”指定した開始数値から、無限に数値を生成し続けます。
import itertools
# 10から始まり、2ずつ増える無限イテレーターcounter = itertools.count(start=10, step=2)
print(next(counter)) # 10print(next(counter)) # 12print(next(counter)) # 143.2 無限に繰り返す:cycle()
Section titled “3.2 無限に繰り返す:cycle()”指定したリストや文字列の要素を、最後まで到達したら最初に戻って無限に返し続けます。
import itertools
# ONとOFFを交互に繰り返すスイッチswitch = itertools.cycle(['ON', 'OFF'])
print(next(switch)) # ONprint(next(switch)) # OFFprint(next(switch)) # ONこれらの itertools 関数を組み合わせることで、複雑なデータ処理をシンプルかつ高速、そしてメモリ効率良く実装することができます。