Search code examples
pythonpython-3.xcombinationspython-itertoolscartesian-product

Cartesian product of nested dictionaries of lists


I have some code that generates all the combinations for a dictionary of lists

import itertools
import collections

def gen_combinations(d):
    keys, values = d.keys(), d.values()
    combinations = itertools.product(*values)

    for c in combinations:
        yield dict(zip(keys, c))

Let's say I have a dictionary A like

A = {'a': [0, 1],
     'b': [2, 3, 4]}

It yields:

{'a': 0, 'b': 2}
{'a': 0, 'b': 3}
{'a': 0, 'b': 4}
{'a': 1, 'b': 2}
{'a': 1, 'b': 3}
{'a': 1, 'b': 4}

Now I'd like it to work with nested dictionary of lists like

B = {'s1': {'a': [0, 1],
            'b': [0, 1, 2] },
     's2': {'c': [0, 1],
            'd': [0, 1] }}

which should yield something like:

{'s1': {'a': 0, 'b': 0},
 's2': {'c': 0, 'd': 0}}

{'s1': {'a': 0, 'b': 0},
 's2': {'c': 0, 'd': 1}}

{'s1': {'a': 0, 'b': 0},
 's2': {'c': 1, 'd': 0}}

{'s1': {'a': 0, 'b': 0},
 's2': {'c': 1, 'd': 1}}

and so on for all the combinations... I'm looking for an elegant way to do this. Don't have any particular performance restrictions, they are small dictionaries.


Solution

  • Just create the product of the output of gen_combinations() for each key in the outer dictionary:

    def gen_dict_combinations(d):
        keys, values = d.keys(), d.values()
        for c in itertools.product(*(gen_combinations(v) for v in values)):
            yield dict(zip(keys, c))
    

    This is basically the same pattern, only now instead of using values directly, we use the output of gen_combinations():

    >>> for c in gen_dict_combinations(B):
    ...     print(c)
    ...
    {'s1': {'a': 0, 'b': 0}, 's2': {'c': 0, 'd': 0}}
    {'s1': {'a': 0, 'b': 0}, 's2': {'c': 0, 'd': 1}}
    {'s1': {'a': 0, 'b': 0}, 's2': {'c': 1, 'd': 0}}
    {'s1': {'a': 0, 'b': 0}, 's2': {'c': 1, 'd': 1}}
    {'s1': {'a': 0, 'b': 1}, 's2': {'c': 0, 'd': 0}}
    {'s1': {'a': 0, 'b': 1}, 's2': {'c': 0, 'd': 1}}
    {'s1': {'a': 0, 'b': 1}, 's2': {'c': 1, 'd': 0}}
    {'s1': {'a': 0, 'b': 1}, 's2': {'c': 1, 'd': 1}}
    {'s1': {'a': 0, 'b': 2}, 's2': {'c': 0, 'd': 0}}
    {'s1': {'a': 0, 'b': 2}, 's2': {'c': 0, 'd': 1}}
    {'s1': {'a': 0, 'b': 2}, 's2': {'c': 1, 'd': 0}}
    {'s1': {'a': 0, 'b': 2}, 's2': {'c': 1, 'd': 1}}
    {'s1': {'a': 1, 'b': 0}, 's2': {'c': 0, 'd': 0}}
    {'s1': {'a': 1, 'b': 0}, 's2': {'c': 0, 'd': 1}}
    {'s1': {'a': 1, 'b': 0}, 's2': {'c': 1, 'd': 0}}
    {'s1': {'a': 1, 'b': 0}, 's2': {'c': 1, 'd': 1}}
    {'s1': {'a': 1, 'b': 1}, 's2': {'c': 0, 'd': 0}}
    {'s1': {'a': 1, 'b': 1}, 's2': {'c': 0, 'd': 1}}
    {'s1': {'a': 1, 'b': 1}, 's2': {'c': 1, 'd': 0}}
    {'s1': {'a': 1, 'b': 1}, 's2': {'c': 1, 'd': 1}}
    {'s1': {'a': 1, 'b': 2}, 's2': {'c': 0, 'd': 0}}
    {'s1': {'a': 1, 'b': 2}, 's2': {'c': 0, 'd': 1}}
    {'s1': {'a': 1, 'b': 2}, 's2': {'c': 1, 'd': 0}}
    {'s1': {'a': 1, 'b': 2}, 's2': {'c': 1, 'd': 1}}