Explain Codes LogoExplain Codes Logo

All combinations of a list of lists

python
performance
recursion
nested-loops
Anton ShumikhinbyAnton Shumikhin·Jan 13, 2025
TLDR

Effortlessly leveraging itertools.product will generate all combinations from a list of lists in Python. In layman's terms, it cooks up a cartesian product and serves tuples containing ingredients from each list.

The code to bake fresh tuples looks something like this:

from itertools import product lists = [[1, 2], [3, 4], [5, 6]] print(list(product(*lists)) // *pours ingredients into our itertools.product oven*

Your freshly-baked tuples will look something like this:

[(1, 3, 5), ..., (2, 4, 6)]

Replace lists with your ingredients (data), fire up the oven and see the results!

How to handle curveballs (edge cases) and efficiency using itertools

Ever hit a curveball of empty lists? itertools.product effortlessly fields empty results, eliminating the need to dive into the complexities of algorithmic troubleshooting.

When you're in a race against time, performance is key. itertools.product has got your back because it's optimized at C level in Python. It's like having an F1 car at your disposal for your coding race.

Going off-road with recursion, when itertools can't join the party

No itertools? No problem. Set sail with Recursion towards a custom implementation:

def combine(list_of_lists, prefix=[]): if not list_of_lists: yield tuple(prefix) else: for item in list_of_lists[0]: yield from combine(list_of_lists[1:], prefix + [item]) # Example usage: for combination in combine([[1,2], [3], [4,5]]): print(combination) // "Can you hear me now?" - Recursion, probably

This little trick of ours keeps external dependencies at bay and is a jack of all trades, easily adapting to older Python versions or environments where itertools is non-existent.

Manual labor with nested loops

Sometimes, getting your hands dirty with nested loops gives a straightforward way to mix elements when itertools or recursion seems unnecessary:

lists = [[1, 2], [3, 4], [5, 6]] combinations = [] for a in lists[0]: for b in lists[1]: for c in lists[2]: combinations.append((a, b, c)) print(combinations)

But remember: with nested loops, your code may start looking like Inception; you’ll need a nested loop for each list. It's not as flexible or elegant as itertools.product.

Numpy's solution for number-crunchers

Numpy buffs who dabble with numerical data can find their safe haven in numpy.meshgrid, followed by numpy.reshape to catch combinations in array format:

import numpy as np lists = [np.array(x) for x in [[1, 2], [3, 4], [5, 6]]] mesh = np.meshgrid(*lists) combinations = np.vstack(map(np.ravel, mesh)).T print(combinations.tolist())

This logic is like your secret weapon when dealing with large-scale numerical computations that crave — you've guessed it! — for NumPy's excellent performance.

Welcoming diversity in data structures

Whoever said different data types, such as strings and numbers, cause trouble? No more. itertools.product and custom recursive functions can handle these like a Swiss Army Knife.

from itertools import product lists = [['apple', 'banana'], [1, 2, 3], ['x', 'y']] print(list(product(*lists))) // Edible numbers and countable fruits? More power to Python!

Tailoring the output to fit your needs

In some galaxies, it's common to need specific output formats, say, lists of lists. All we have to do is drop a list comprehension wrapper over the product call to mold your output:

from itertools import product lists = [[1, 2], [3, 4], [5, 6]] combinations = [list(combination) for combination in product(*lists)] print(combinations)

Slow and steady with yield

def all_combinations(lists): if len(lists) == 1: for item in lists[0]: yield (item,) else: for item in lists[0]: for rest in all_combinations(lists[1:]): yield (item,) + rest # Example Usage: for combination in all_combinations([[1,2], [3,4], [5,6]]): print(combination)

Dealing with a crowd: variable number of lists

Finally, whether you have a line of two or a crowd of a thousand. Both itertools.product and recursive approaches can handle them. The key is the use of argument unpacking (*) with itertools or proper recursion base and recursive calls.

This adaptability ensures that you can handle a wide range of input sizes and types, while maintaining the elegance and flexibility of your solution.