简体   繁体   中英

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

I'm assuming the ordering of the optional layers matter, so you can just iteratively create all combinations of the optional layers, then use itertools.product on the layers + optional_layers to generate the lists.

import itertools
from pprint import pprint

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

layers = [l1, l2]
optional_layers = [l3, l4]

results = []
results += itertools.product(*layers)
for i in range(len(optional_layers) + 1):
    comb = itertools.combinations(optional_layers, r=i)
    for c in comb:
        results += itertools.product(*layers, *c)

pprint(results)

Output

[('A', 'C'),
 ('B', 'C'),
 ('A', 'C'),
 ('B', 'C'),
 ('A', 'C', 'D'),
 ('A', 'C', 'E'),
 ('B', 'C', 'D'),
 ('B', 'C', 'E'),
 ('A', 'C', 'F'),
 ('A', 'C', 'G'),
 ('B', 'C', 'F'),
 ('B', 'C', 'G'),
 ('A', 'C', 'D', 'F'),
 ('A', 'C', 'D', 'G'),
 ('A', 'C', 'E', 'F'),
 ('A', 'C', 'E', 'G'),
 ('B', 'C', 'D', 'F'),
 ('B', 'C', 'D', 'G'),
 ('B', 'C', 'E', 'F'),
 ('B', 'C', 'E', 'G')]

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')

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM