When you need all possible subgroups of a list, such as pairs, triples, or any size up to N, Python's itertools module provides efficient, production-ready solutions.
This guide covers standard approaches, memory-efficient patterns, and practical applications for combinatorial generation.
Using itertools.combinations (Standard Approach)
To get all combinations up to size N, iterate through each length from 1 to N:
from itertools import combinations
items = ['A', 'B', 'C']
max_size = 2
results = []
for r in range(1, max_size + 1):
for combo in combinations(items, r):
results.append(combo)
print(results)Using chain.from_iterable (Memory Efficient)
For cleaner code and better memory efficiency, chain the iterators into a single lazy stream:
from itertools import combinations, chain
items = [1, 2, 3]
max_size = 2
combos = chain.from_iterable(
combinations(items, r) for r in range(1, max_size + 1)
)
print(list(combos))Creating a Reusable Function
Wrap the logic in a function for cleaner code:
from itertools import combinations, chain
def combinations_up_to_n(items, max_size):
"""Generate all combinations from size 1 to max_size."""
return chain.from_iterable(
combinations(items, r) for r in range(1, max_size + 1)
)
items = ['red', 'green', 'blue']
for combo in combinations_up_to_n(items, 2):
print(combo)Output:
('red',)
('green',)
('blue',)
('red', 'green')
('red', 'blue')
('green', 'blue')Generating the Power Set
When you generate combinations up to the full length of the list (including the empty set), you get the power set (all possible subsets):
from itertools import combinations, chain
def power_set(iterable):
"""Generate all subsets including empty set."""
items = list(iterable)
return chain.from_iterable(
combinations(items, r) for r in range(len(items) + 1)
)
print(list(power_set([1, 2, 3])))Including vs Excluding Empty Set
Depending on your use case, you may or may not want the empty combination:
from itertools import combinations, chain
def subsets(items, include_empty=False):
"""Generate subsets with optional empty set."""
start = 0 if include_empty else 1
items = list(items)
return chain.from_iterable(
combinations(items, r) for r in range(start, len(items) + 1)
)
items = ['a', 'b']
print("With empty:", list(subsets(items, include_empty=True)))
print("Without empty:", list(subsets(items, include_empty=False)))Practical Example: Feature Selection
A common use case is testing combinations of features in machine learning:
from itertools import combinations, chain
def evaluate_feature_combinations(features, max_features, evaluate_fn):
"""Test all feature combinations up to max_features."""
results = []
for combo in chain.from_iterable(
combinations(features, r) for r in range(1, max_features + 1)
):
score = evaluate_fn(combo)
results.append((combo, score))
return sorted(results, key=lambda x: x[1], reverse=True)
features = ['age', 'income', 'location', 'history']
def mock_evaluate(feature_set):
return len(feature_set) * 0.1 + 0.5
best_combinations = evaluate_feature_combinations(features, 3, mock_evaluate)
print("Top 3 feature sets:")
for combo, score in best_combinations[:3]:
print(f" {combo}: {score:.2f}")Output:
Top 3 feature sets:
('age', 'income', 'location'): 0.80
('age', 'income', 'history'): 0.80
('age', 'location', 'history'): 0.80Counting Combinations Without Generating
To count combinations without generating them, use the mathematical formula:
from math import comb
def count_combinations_up_to_n(num_items, max_size):
"""Count total combinations without generating them."""
return sum(comb(num_items, r) for r in range(1, max_size + 1))
items = 10
max_size = 3
total = count_combinations_up_to_n(items, max_size)
print(f"Total combinations: {total}") Summary
| Method | Syntax | Memory Usage |
|---|---|---|
| Nested loop | for r in range: for c in combinations |
High (stores all) |
| chain.from_iterable | chain.from_iterable(combinations(...) for r in range) |
Low (lazy) |
| List comprehension | [c for r in range for c in combinations] |
High (stores all) |