Search code examples
pythonalgorithmcombinationspython-itertoolscartesian-product

Cartesian Product with optional lists


I am creating a program, in python, that allows me to generate NFT Art based on the given assets. Obviously the number of arts that can be generated varies according to the assets (layers and layer images) and this is precisely the problem, how can I calculate the possible combinations also counting the optional layers?

To be clearer:

for example I have 4 layers:

l1 = ["A","B"]
l2 = ["C"]
l3 = ["D","E"] #optional
l4 = ["F","G"] #optional

where l3 and l4 are optional. So the combinations I expect are:

 1.  ["A","C"]
 2.  ["B","C"]
 3.  ["A","C","D"]
 4.  ["A","C","E"]
 5.  ["B","C","D"]
 6.  ["B","C","E"]
 7.  ["A","C","F"]
 8.  ["A","C","G"]
 9.  ["B","C","F"]
 10. ["B","C","G"]
 11. ["A","C","D","F"]
 12. ["A","C","D","G"]
 13. ["A","C","E","F"]
 14. ["A","C","E","G"]
 15. ["B","C","D","F"]
 16. ["B","C","D","G"]
 17. ["B","C","E","F"]
 18. ["B","C","E","G"]

How can I get to that? I tried with itertools.product but obviusly it take into account all lists


Solution

  • One way to do this is with the powerset recipe from the itertools docs. Chaining together the products of 'required lists' with every subset of the 'optional-list-set' gives a generator that produces each possibility once:

    def powerset(iterable):
        """powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)"""
        s = list(iterable)
        return chain.from_iterable(combinations(s, r) for r in range(len(s) + 1))
    
    def product_with_optional(required_sequences, optional_sequences):
        return chain.from_iterable(product(*required_sequences, *optionals)
                                   for optionals in powerset(optional_sequences))
    
    optional_combinations = product_with_optional(required_sequences=[l1, l2],
                                                  optional_sequences=[l3, l4])
    

    which gives:

    1 ('A', 'C')
    2 ('B', 'C')
    3 ('A', 'C', 'D')
    4 ('A', 'C', 'E')
    5 ('B', 'C', 'D')
    6 ('B', 'C', 'E')
    7 ('A', 'C', 'F')
    8 ('A', 'C', 'G')
    9 ('B', 'C', 'F')
    10 ('B', 'C', 'G')
    11 ('A', 'C', 'D', 'F')
    12 ('A', 'C', 'D', 'G')
    13 ('A', 'C', 'E', 'F')
    14 ('A', 'C', 'E', 'G')
    15 ('B', 'C', 'D', 'F')
    16 ('B', 'C', 'D', 'G')
    17 ('B', 'C', 'E', 'F')
    18 ('B', 'C', 'E', 'G')