python tuples combinations permutation

python - Combinaciones únicas de una lista de tuplas.



tuples combinations (5)

Dada una lista de 3 tuplas, por ejemplo: [(1,2,3), (4,5,6), (7,8,9)] ¿cómo calcularías todas las combinaciones posibles y combinaciones de subconjuntos?

En este caso, el resultado debería verse así:

[ (1), (1,4), (1,5), (1,6), (1,7), (1,8), (1,9), (1,4,7), (1,4,8), (1,4,9), (1,5,7), (1,5,8), (1,5,9), (1,6,7), (1,6,8), (1,6,9), (2), ..., (3), ..., (4), (4,7), (4,8), (4,9), (5), (5,7), (5,8), (5,9), (6), (6,7), (6,8), (6,9), (7), (8), (9) ]

  • todas las tuplas con elementos idénticos se consideran iguales
  • no se permiten combinaciones que se deriven de las mismas tuplas (por ejemplo, no deberían estar en la solución: (1,2) , (4,6) o (7,8,9) )

Aquí hay una solución no recursiva con un bucle for simple. La unicidad se aplica aplicando set a la lista de las tuplas de salida.

lsts = [(1,2,3), (4,5,6), (7,8,9)] res = [[]] for lst in lsts: res += [(*r, x) for r in res for x in lst] # print({tuple(lst) for lst in res[1:]}) # {(5, 9), (4, 7), (6, 9), (1, 4, 7), (2, 6, 9), (4, 8), (3, 4, 7), (2, # 8), (2, 6, 8), (9,), (2, 5, 8), (1, 6), (3, 6, 8), (2, 5, 9), (3, 5, # 9), (3, 7), (2, 5), (3, 6, 9), (5, 8), (1, 6, 8), (3, 5, 8), (2, 6, # 7), (4, 9), (6, 7), (1,), (2, 9), (1, 6, 9), (3,), (1, 5), (5,), (3, # 6), (7,), (3, 6, 7), (1, 5, 9), (2, 6), (2, 4, 7), (1, 5, 8), (3, 4, # 8), (8,), (3, 4, 9), (1, 4), (1, 6, 7), (3, 9), (1, 9), (2, 5, 7), (3, # 5), (2, 7), (2, 4, 9), (6, 8), (1, 5, 7), (2,), (2, 4, 8), (5, 7), (1, # 4, 8), (3, 5, 7), (4,), (3, 8), (1, 8), (1, 4, 9), (6,), (1, 7), (3, # 4), (2, 4)}


Gracias a @wovano por aclarar la regla 2. Esto hace que la solución sea aún más corta:

from itertools import chain, combinations, product blubb = [(1, 2, 3), (4, 5, 6), (7, 8, 9)] set_combos = chain.from_iterable(combinations(blubb, i) for i in range(len(blubb) + 1)) result_func = list(chain.from_iterable(map(lambda x: product(*x), set_combos)))

Y como beneficio adicional, una comparación de velocidad. La solución de @ hilberts_drinking_problem es increíble, pero hay una sobrecarga.

def pure_python(list_of_tuples): res = [tuple()] for lst in list_of_tuples: res += [(*r, x) for r in res for x in lst] return res def with_itertools(list_of_tuples): set_combos = chain.from_iterable(combinations(list_of_tuples, i) for i in range(len(list_of_tuples) + 1)) return list(chain.from_iterable(map(lambda x: product(*x), set_combos))) assert sorted(with_itertools(blubb), key=str) == sorted(pure_python(blubb), key=str)

Ambos calculan lo mismo, pero ...

%timeit with_itertools(blubb) 7.18 µs ± 11.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) %timeit pure_python(blubb) 10.5 µs ± 46 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

aunque para muestras pequeñas las itertools son un poco más rápidas, existe un gran vacío cuando el conjunto crece:

import toolz large_blubb = list(toolz.partition_all(3, range(3*10))) assert sorted(with_itertools(large_blubb), key=str) == sorted(pure_python(large_blubb), key=str) %timeit with_itertools(large_blubb) 106 ms ± 307 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) %timeit pure_python(large_blubb) 262 ms ± 1.85 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

lo que hace que la solución de itertools sea ​​2,5 veces más rápida.

Edición: corregida de acuerdo con la regla 2. Además, comparación de velocidad Edición: corrija otro error: ahora la comparación de velocidad es realista


Otra version:

from itertools import product, combinations lst = [(1,2,3), (4,5,6), (7,8,9)] def generate(lst): for idx in range(len(lst)): for val in lst[idx]: yield (val,) for j in range(1, len(lst)): for c in combinations(lst[idx+1:], j): yield from tuple((val,) + i for i in product(*c)) l = [*generate(lst)] print(l)

Huellas dactilares:

[(1,), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (1, 9), (1, 4, 7), (1, 4, 8), (1, 4, 9), (1, 5, 7), (1, 5, 8), (1, 5, 9), (1, 6, 7), (1, 6, 8), (1, 6, 9), (2,), (2, 4), (2, 5), (2, 6), (2, 7), (2, 8), (2, 9), (2, 4, 7), (2, 4, 8), (2, 4, 9), (2, 5, 7), (2, 5, 8), (2, 5, 9), (2, 6, 7), (2, 6, 8), (2, 6, 9), (3,), (3, 4), (3, 5), (3, 6), (3, 7), (3, 8), (3, 9), (3, 4, 7), (3, 4, 8), (3, 4, 9), (3, 5, 7), (3, 5, 8), (3, 5, 9), (3, 6, 7), (3, 6, 8), (3, 6, 9), (4,), (4, 7), (4, 8), (4, 9), (5,), (5, 7), (5, 8), (5, 9), (6,), (6, 7), (6, 8), (6, 9), (7,), (8,), (9,)]


Puede usar la recursividad con un generador:

data = [(1,2,3), (4,5,6), (7,8,9)] def combos(d, c = []): if len(c) == len(d): yield c else: for i in d: if i not in c: yield from combos(d, c+[i]) def product(d, c = []): if c: yield tuple(c) if d: for i in d[0]: yield from product(d[1:], c+[i]) result = sorted({i for b in combos(data) for i in product(b)}) final_result = [a for i, a in enumerate(result) if all(len(c) != len(a) or len(set(c)&set(a)) != len(a) for c in result[:i])]

Salida:

[(1,), (1, 4), (1, 4, 7), (1, 4, 8), (1, 4, 9), (1, 5), (1, 5, 7), (1, 5, 8), (1, 5, 9), (1, 6), (1, 6, 7), (1, 6, 8), (1, 6, 9), (1, 7), (1, 8), (1, 9), (2,), (2, 4), (2, 4, 7), (2, 4, 8), (2, 4, 9), (2, 5), (2, 5, 7), (2, 5, 8), (2, 5, 9), (2, 6), (2, 6, 7), (2, 6, 8), (2, 6, 9), (2, 7), (2, 8), (2, 9), (3,), (3, 4), (3, 4, 7), (3, 4, 8), (3, 4, 9), (3, 5), (3, 5, 7), (3, 5, 8), (3, 5, 9), (3, 6), (3, 6, 7), (3, 6, 8), (3, 6, 9), (3, 7), (3, 8), (3, 9), (4,), (4, 7), (4, 8), (4, 9), (5,), (5, 7), (5, 8), (5, 9), (6,), (6, 7), (6, 8), (6, 9), (7,), (8,), (9,)]


Usando itertools :

import itertools as it def all_combinations(groups): result = set() for prod in it.product(*groups): for length in range(1, len(groups) + 1): result.update(it.combinations(prod, length)) return result all_combinations([(1,2,3), (4,5,6), (7,8,9)])