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