简体   繁体   中英

Finding combination of combinations

Let's say we have a list of lists:

list = [[a],[b],[c],[d],[e],[f],[g],[h]]

Now I am looking to generate all possible combinations of 2 by 3, so that one possible combination would be:

[[[a],[b],[c]], [[d],[e],[f]]]

and another would be:

[[[g],[h],[c]], [[d],[e],[f]]]

or

[[[a],[b],[f]], [[d],[e],[c]]]

The order does not matter at any level. However, elements must not repeat, meaning the following list would be incorrect, and should not be generated:

[[[a],[b],[f]], [[a],[e],[f]]]

Similarly

 [[a,b,c], [e,f,c]]   and   [[e,f,c], [a,b,c]]

would be the same thing, and should only appear once.

I have fried quite a few nerve cells, but have been unable to produce a working solution. I am using Python to solve this problem.

You can use a recursive generator function:

lst = [['a'],['b'],['c'],['d'],['e'],['f'],['g'],['h']]
x = 3
def combos(lst, n, c = []):
   if sum(map(len, c)) == (l:=len(lst))-(l%x):
      yield c
   else:
      for i in filter(lambda x:not any(x in i for i in c), lst):
         if not c or len(c[-1]) == n:
             yield from combos(lst, n, c+[[i]])
         else:
             yield from combos(lst, n, [*c[:-1], c[-1]+[i]])

result = list(combos(lst, x))
print(result[:10])

Output:

[[['a'], ['b'], ['c']], [['d'], ['e'], ['f']]]
[[['a'], ['b'], ['c']], [['d'], ['e'], ['g']]]
[[['a'], ['b'], ['c']], [['d'], ['e'], ['h']]]
[[['a'], ['b'], ['c']], [['d'], ['f'], ['e']]]
[[['a'], ['b'], ['c']], [['d'], ['f'], ['g']]]
[[['a'], ['b'], ['c']], [['d'], ['f'], ['h']]]
[[['a'], ['b'], ['c']], [['d'], ['g'], ['e']]]
[[['a'], ['b'], ['c']], [['d'], ['g'], ['f']]]
[[['a'], ['b'], ['c']], [['d'], ['g'], ['h']]]
[[['a'], ['b'], ['c']], [['d'], ['h'], ['e']]]
...

The itertools.permutations -function is what you are looking for. Your problem can actually be solved by creating all permutations of 6 elements and then simply splitting all those permutations into two lists.

This code should solve your problem:

>>> from itertools import permutations
>>> lst = [['a'],['b'],['c'],['d'],['e'],['f'],['g'],['h']]

>>> [(x[:3], x[3:]) for x in permutations(lst, 6)]
[((['a'], ['b'], ['c']), (['d'], ['e'], ['f'])),
 ((['a'], ['b'], ['c']), (['d'], ['e'], ['g'])),
 ((['a'], ['b'], ['c']), (['d'], ['e'], ['h'])),
 ((['a'], ['b'], ['c']), (['d'], ['f'], ['g'])),
 ((['a'], ['b'], ['c']), (['d'], ['f'], ['h'])),
 ((['a'], ['b'], ['c']), (['d'], ['g'], ['h'])),
 ...

It is not just simple, it is also fast:

>>> %timeit [(x[:3], x[3:]) for x in permutations(lst, 6)]
7.32 ms ± 94.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

itertools is a popular library for taking some of the mental load out of these types of problems.

One solution (check that this works for your use case though, in case you forgot to mention an ordering requirement) that leverages itertools:

import itertools
l = [['a'],['b'],['c'],['d'],['e'],['f'],['g'],['h']]
threecombs = list(itertools.combinations(l, r=3))
twocombs = []
for comb1, comb2 in itertools.product(threecombs, threecombs):
   if all(c not in comb2 for c in comb1):
       twocombs.append([list(comb1), list(comb2)])

this can be sped up if your list is quite large by not converting threecombs to a list, but I'll leave that to you if you'd like to do that since your question suggested you're working with lists as opposed to generators anyway.

itertools has the magic you seek

Concise form (substitute your lists for list_proxy ):

from itertools import combinations, permutations

list_proxy = [c for c in 'abcdefgh']
sextets = list(combinations(list_proxy, 6))
permute_sextet = lambda s: list(permutations(s))
all_permuted_sextets = [p for sublist in (permute_sextet(s) for s in sextets) for p in sublist]
pair_of_triple = lambda p: [list(p[:3]), list(p[3:6])]
all_pairs_of_triples = [pair_of_triple(pot) for pot in all_permuted_sextets]

This version helps for understanding what is going on with the individual steps:

from itertools import combinations, permutations, chain
list_proxy = [c for c in 'abcdefgh']
print(list_proxy)
# ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']

sextets = list(combinations(list_proxy, 6))
print(len(sextets))
# 28

print(sextets[0], type(sextets[0]))
# ('a', 'b', 'c', 'd', 'e', 'f') <class 'tuple'>

permute_sextet = lambda s: list(permutations(s))
p0 = permute_sextet(sextets[0])
p1 = permute_sextet(sextets[1])
print(len(p0), type(p0), len(p1), type(p1))
# 720 <class 'list'> 720 <class 'list'>

p_first_two = list(chain(p0, p1))
print(len(p_first_two), type(p_first_two))
# 1440 <class 'list'>

all_permuted_sextets = [p for sublist in (permute_sextet(s) for s in sextets) for p in sublist]
print(f'all_permuted_sextets has len {len(all_permuted_sextets)}')
# all_permuted_sextets has len 20160

print(f'all_permuted_sextets[0] has len {len(all_permuted_sextets[0])} and has {all_permuted_sextets[0]}')
# all_permuted_sextets[0] has len 6 and has ('a', 'b', 'c', 'd', 'e', 'f')

pair_of_triple = lambda p: [list(p[:3]), list(p[3:6])]
print(pair_of_triple('abcdef'))
# [['a', 'b', 'c'], ['d', 'e', 'f']]

all_pairs_of_triples = [pair_of_triple(pot) for pot in all_permuted_sextets]
print(f'all_pairs_of_triples has len {len(all_pairs_of_triples)}')
# all_pairs_of_triples has len 20160
print(f'all_pairs_of_triples[340] is {all_pairs_of_triples[340]}')
# all_pairs_of_triples[340] is [['c', 'f', 'a'], ['e', 'b', 'd']]

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