Pythonでデータ分析やアルゴリズムの構築を行う際、複数の要素から特定の数を選び出す「組み合わせ」の計算が必要になる場面は多々あります。
標準ライブラリのitertoolsモジュールに含まれるitertools.combinationsは、こうした処理を効率的に行うための強力なツールです。
手動でループを回して組み合わせを実装することも可能ですが、標準ライブラリを使うことで実行速度の向上とコードの簡略化を同時に実現できます。
本記事では、itertools.combinationsの基本的な使い方から、実務で役立つ応用手法まで詳しく解説します。
itertools.combinationsとは?
itertools.combinationsは、指定したデータ(イテラブル)から、重複のない特定の長さの組み合わせを生成する関数です。
この関数の最大の特徴は、数学的な「組み合わせ(nCr)」を生成する点にあります。
順列(Permutations)とは異なり、選ぶ順番が違っても中身の要素が同じであれば、それは同一の組み合わせとして扱われます。
基本的な構文
まずは、itertools.combinationsの構文を確認しましょう。
import itertools
# itertools.combinations(iterable, r)
# iterable: 対象となるリストや文字列などのイテラブルオブジェクト
# r: 選択する要素の数
この関数は、生成された組み合わせを一つずつ返すイテレータ(Iterator)を返却します。
そのため、大量の組み合わせを生成する場合でも、メモリを一度に消費しすぎないというメリットがあります。
itertools.combinationsの基本的な使い方
もっともシンプルな例として、3つの要素を持つリストから2つの要素を選ぶ組み合わせを生成してみましょう。
以下のコードでは、itertools.combinationsを使用して結果を出力しています。
import itertools
# 元となるリストを定義
items = ['A', 'B', 'C']
# 3つの中から2つを選ぶ組み合わせを生成
comb = itertools.combinations(items, 2)
# リスト形式に変換して表示
print(list(comb))
[('A', 'B'), ('A', 'C'), ('B', 'C')]
結果を見ると、(‘A’, ‘B’) は存在しますが、(‘B’, ‘A’) は含まれていないことがわかります。
これは、itertools.combinationsが要素の並び順を考慮しないためです。
数値リストでの利用例
数値のリストに対しても同様に使用することができます。
import itertools
numbers = [1, 2, 3, 4]
# 4つの中から3つを選ぶ
result = list(itertools.combinations(numbers, 3))
print(result)
[(1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4)]
このように、指定した長さ r に基づいてすべてのパターンを網羅してくれます。
combinationsとpermutationsの違い
itertoolsには、似た機能を持つpermutations(順列)も存在します。
この2つの違いを正しく理解しておくことは、プログラムのバグを防ぐために非常に重要です。
| 関数名 | 意味 | 順序の区別 | 例 ([‘A’, ‘B’], 2) |
|---|---|---|---|
combinations | 組み合わせ | なし | (‘A’, ‘B’) のみ |
permutations | 順列 | あり | (‘A’, ‘B’), (‘B’, ‘A’) |
例えば、対戦表を作成する場合などで「Aチーム対Bチーム」と「Bチーム対Aチーム」を区別しないのであれば、combinationsを使用するのが適切です。
itertools.combinationsの応用
基本を理解したところで、より実践的な活用方法を見ていきましょう。
重複を許す組み合わせ:combinations_with_replacement
通常のcombinationsでは、同じ要素を複数回選ぶことはできません。
しかし、「同じ要素を繰り返し選んでも良い」という条件下で組み合わせを作りたい場合は、itertools.combinations_with_replacementを使用します。
import itertools
items = ['A', 'B']
# 重複を許して2つ選ぶ
res_with_replacement = list(itertools.combinations_with_replacement(items, 2))
print(res_with_replacement)
[('A', 'A'), ('A', 'B'), ('B', 'B')]
このメソッドを使うと、(‘A’, ‘A’) のような自分自身との組み合わせも生成されるようになります。
特定の合計値になる組み合わせを抽出する
リストの中から、合計が特定の数値になる組み合わせを探す処理は、アルゴリズム問題でもよく登場します。
combinationsとリスト内包表記を組み合わせることで、スマートに記述できます。
import itertools
numbers = [1, 2, 3, 4, 5, 6]
target = 7
# 2つの数字の合計が7になる組み合わせを探す
pairs = [c for c in itertools.combinations(numbers, 2) if sum(c) == target]
print(pairs)
[(1, 6), (2, 5), (3, 4)]
このように、条件分岐と組み合わせることで必要なデータだけを効率よく抽出できます。
すべての要素数パターンの組み合わせを取得する
1つの要素を選ぶ場合から、すべての要素を選ぶ場合まで、すべての組み合わせ(べき集合のようなパターン)を取得したいケースもあります。
その場合は、ループの中で r の値を変化させていきます。
import itertools
items = ['A', 'B', 'C']
for r in range(1, len(items) + 1):
for combo in itertools.combinations(items, r):
print(combo)
('A',)
('B',)
('C',)
('A', 'B')
('A', 'C')
('B', 'C')
('A', 'B', 'C')
パフォーマンスに関する注意点
itertools.combinationsは非常に高速ですが、扱うデータの数(n)や選択する数(r)が大きくなると、組み合わせの数は爆発的に増加します。
例えば、100個の要素から10個を選ぶ組み合わせは17兆通りを超えます。
このようなケースで list() を使ってメモリ上にすべて展開しようとすると、メモリ不足(MemoryError)でプログラムがクラッシュする原因になります。
大きなデータを扱う際は、list() に変換せず、for 文などで直接イテレータとして処理するようにしましょう。
# 推奨:イテレータのまま処理する
comb_iter = itertools.combinations(range(100), 5)
for c in comb_iter:
# 必要な処理だけをここで行う
pass
break # 実際には非常に多いため途中で止める例
まとめ
Pythonのitertools.combinationsは、数学的な組み合わせを効率的に生成するための非常に便利な関数です。
自分で複雑なループ処理を書く必要がなく、可読性の高いコードを維持しながら高度な計算を行うことができます。
今回解説したpermutationsとの違いや、重複を許す場合のcombinations_with_replacementの使い分けをマスターすることで、Pythonでのプログラミング効率は格段に向上します。
ぜひ、データ分析や問題解決の現場で活用してみてください。
