Search code examples
pythondictionarypermutationpython-itertools

get permutations of dict nested with dicts


Really struggling to find a good solution for this problem.

assume I have the following dict:

items = {
    "item_name": {
        "apple": {"color": ["red", "blue"]},
        "banana": {"color": ["yellow", "red"]}
        },
    "type": ["ripe", "not_ripe"]
}

and I want to generate the following output:

[
    {"item_name": "apple", "colors": "red", "type": "ripe"},
    {"item_name": "apple", "colors": "blue", "type": "ripe"},
    {"item_name": "apple", "colors": "red", "type": "not_ripe"},
    {"item_name": "apple", "colors": "blue", "type": "not_ripe"},
    {"item_name": "banana", "colors": "yellow", "type": "ripe"},
    {"item_name": "banana", "colors": "red", "type": "ripe"},
    {"item_name": "banana", "colors": "yellow", "type": "not_ripe"},
    {"item_name": "banana", "colors": "red", "type": "not_ripe"},
]

So I want the cartesion product of item_name x color x type, but the possible values for color are different for each item_name (possibly overlapping), so it is not sufficient to just permute all the dict keys with itertools.product as described e.g. in this question

This is different from the problems I have found on SO, as far as I see it there are question on resolving nested dicts, but only if the sub-elements are lists (not dicts, as it is the case in my question), for example here or here.

I would really like to show anything that i have tried but so far I canceled each approach for being too complex.

Is there a straightforward to achieve the desired result? It would also be an option to change the structure of items in a way that the logic is preserved.


Solution

  • Since each item has unique properties (i.e. 'colors'), looping over the items is the intuitive solution:

    import itertools
    
    items = {
        "item_name": {
            "apple": {"color": ["red", "blue"]},
            "banana": {"color": ["yellow", "red"]}
            },
        "type": ["ripe", "not_ripe"]
    }
    
    items_product = []
    for item, properties in items["item_name"].items():
        items_product.extend(
            [dict(zip(("type", *properties.keys(), "item_name"), (*values, item)))
             for values in itertools.product(items["type"], *properties.values())])
    
    print(items_product)
    

    Horrendous one-liner is also an option:

    items_product = [
        comb for item, properties in items["item_name"].items()
        for comb in (
            dict(
                zip(("item_name", "type", *properties.keys()),
                    (item, *values))
            )
             for values in itertools.product(items["type"], *properties.values())
        )
    ]
    

    I hope you understand this is a fertile soil for having nightmares.