¿Obtener el producto cartesiano de una serie de listas?

317

¿Cómo puedo obtener el producto cartesiano (todas las combinaciones posibles de valores) de un grupo de listas?

Entrada:

somelists = [
   [1, 2, 3],
   ['a', 'b'],
   [4, 5]
]

Salida deseada:

[(1, 'a', 4), (1, 'a', 5), (1, 'b', 4), (1, 'b', 5), (2, 'a', 4), (2, 'a', 5) ...]
ʞɔıu
fuente
24
tenga en cuenta que 'todas las combinaciones posibles' no son lo mismo que 'producto cartesiano', ya que en los productos cartesianos se permiten duplicados.
Tríptico
77
¿Existe una versión no duplicada del producto cartesiano?
KJW
16
@KJW Sí,set(cartesian product)
NoBugs
55
No debe haber duplicados en un producto cartesiano, a menos que las listas de entrada contengan duplicados. Si no desea duplicados en el producto cartesiano, use set(inputlist)sobre todas sus listas de entrada. No en el resultado.
CamilB
@ Tríptico, ¿qué? La definición estándar de un producto cartesiano es un conjunto. ¿Por qué tantas personas votan?
PascalIv

Respuestas:

378

itertools.product

Disponible desde Python 2.6.

import itertools

somelists = [
   [1, 2, 3],
   ['a', 'b'],
   [4, 5]
]
for element in itertools.product(*somelists):
    print(element)

Que es lo mismo que

for element in itertools.product([1, 2, 3], ['a', 'b'], [4, 5]):
    print(element)
Tríptico
fuente
22
Solo quería agregar el carácter '*' es obligatorio si utiliza las listas de variables según lo dispuesto por el OP.
Brian Buck
1
@jaska: product()genera nitems_in_a_list ** nlistselementos en el resultado ( reduce(mul, map(len, somelists))). No hay ninguna razón para creer que producir un solo elemento no está O(nlists)(amortizado), es decir, la complejidad del tiempo es la misma que para los forbucles anidados simples , por ejemplo, para la entrada en la pregunta:, nlists=3número total de elementos en el resultado: 3*2*2y cada elemento tiene nlistselementos ( 3en este caso).
jfs
2
¿De qué sirven los *somelistas anteriores? ¿Qué hace?
Vineet Kumar Doshi
66
@VineetKumarDoshi: Aquí se utiliza para desbloquear una lista en múltiples argumentos para la llamada a la función. Lea más aquí: stackoverflow.com/questions/36901/…
Moberg
44
Nota: Esto funciona solo si cada lista contiene al menos un elemento
igo
84
import itertools
>>> for i in itertools.product([1,2,3],['a','b'],[4,5]):
...         print i
...
(1, 'a', 4)
(1, 'a', 5)
(1, 'b', 4)
(1, 'b', 5)
(2, 'a', 4)
(2, 'a', 5)
(2, 'b', 4)
(2, 'b', 5)
(3, 'a', 4)
(3, 'a', 5)
(3, 'b', 4)
(3, 'b', 5)
>>>
Jason Baker
fuente
38

Para Python 2.5 y anteriores:

>>> [(a, b, c) for a in [1,2,3] for b in ['a','b'] for c in [4,5]]
[(1, 'a', 4), (1, 'a', 5), (1, 'b', 4), (1, 'b', 5), (2, 'a', 4), 
 (2, 'a', 5), (2, 'b', 4), (2, 'b', 5), (3, 'a', 4), (3, 'a', 5), 
 (3, 'b', 4), (3, 'b', 5)]

Aquí hay una versión recursiva de product()(solo una ilustración):

def product(*args):
    if not args:
        return iter(((),)) # yield tuple()
    return (items + (item,) 
            for items in product(*args[:-1]) for item in args[-1])

Ejemplo:

>>> list(product([1,2,3], ['a','b'], [4,5])) 
[(1, 'a', 4), (1, 'a', 5), (1, 'b', 4), (1, 'b', 5), (2, 'a', 4), 
 (2, 'a', 5), (2, 'b', 4), (2, 'b', 5), (3, 'a', 4), (3, 'a', 5), 
 (3, 'b', 4), (3, 'b', 5)]
>>> list(product([1,2,3]))
[(1,), (2,), (3,)]
>>> list(product([]))
[]
>>> list(product())
[()]
jfs
fuente
La versión recursiva no funciona si algunos argsson iteradores.
jfs
20

con itertools.product :

import itertools
result = list(itertools.product(*somelists))
SilentGhost
fuente
66
¿De qué sirven los *somelistas anteriores?
Vineet Kumar Doshi
@VineetKumarDoshi "producto (somelists)" es un producto cartesiano entre las sublistas de una manera que Python primero obtiene "[1, 2, 3]" como elemento y luego obtiene otro elemento después del próximo comando y ese es el salto de línea, así que el primer producto el término es ([1, 2, 3],), similar para el segundo ([4, 5],) y así "[([1, 2, 3],), ([4, 5],), ( [6, 7],)] " . Si desea obtener un producto cartesiano entre los elementos dentro de las tuplas, debe informar a Python con Asterisk sobre la estructura de las tuplas. Para el diccionario, usas **. Más aquí .
hhh
19

Usaría la comprensión de la lista:

somelists = [
   [1, 2, 3],
   ['a', 'b'],
   [4, 5]
]

cart_prod = [(a,b,c) for a in somelists[0] for b in somelists[1] for c in somelists[2]]
usuario1035648
fuente
1
Realmente me gusta esta solución usando listas de comprensión. No sé por qué no se vota más, es tan simple.
llekn
20
@llekn porque el código parece estar fijado al número de listas
Bằng Rikimaru
11

Aquí hay un generador recursivo, que no almacena ninguna lista temporal

def product(ar_list):
    if not ar_list:
        yield ()
    else:
        for a in ar_list[0]:
            for prod in product(ar_list[1:]):
                yield (a,)+prod

print list(product([[1,2],[3,4],[5,6]]))

Salida:

[(1, 3, 5), (1, 3, 6), (1, 4, 5), (1, 4, 6), (2, 3, 5), (2, 3, 6), (2, 4, 5), (2, 4, 6)]
Anurag Uniyal
fuente
1
Sin embargo, están almacenados en la pila.
Quentin Pradet
@QuentinPradet, ¿quieres decir que un generador como def f(): while True: yield 1este seguirá aumentando su tamaño de pila a medida que avanzamos?
Anurag Uniyal
@QuentinPradet sí, pero incluso en este caso solo la pila necesaria para la profundidad máxima, no toda la lista, así que en este caso la pila de 3
Anurag Uniyal
Es verdad, lo siento. Un punto de referencia podría ser interesante. :)
Quentin Pradet
11

En Python 2.6 y superior puedes usar 'itertools.product`. En versiones anteriores de Python, puede usar el siguiente código equivalente (casi ver documentación) de la documentación , al menos como punto de partida:

def product(*args, **kwds):
    # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy
    # product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111
    pools = map(tuple, args) * kwds.get('repeat', 1)
    result = [[]]
    for pool in pools:
        result = [x+[y] for x in result for y in pool]
    for prod in result:
        yield tuple(prod)

El resultado de ambos es un iterador, por lo que si realmente necesita una lista para el procesamiento adicional, úselo list(result).


fuente
Según la documentación, la implementación real de itertools.product NO genera resultados intermedios, lo que podría ser costoso. El uso de esta técnica podría salirse de control con bastante rapidez para listas de tamaño moderado.
Tríptico
44
Solo puedo señalar el OP a la documentación, no leerlo para él.
1
El código de la documentación está destinado a demostrar lo que hace la función del producto, no como una solución alternativa para versiones anteriores de Python.
Triptych
9

Aunque ya hay muchas respuestas, me gustaría compartir algunos de mis pensamientos:

Enfoque iterativo

def cartesian_iterative(pools):
  result = [[]]
  for pool in pools:
    result = [x+[y] for x in result for y in pool]
  return result

Enfoque recursivo

def cartesian_recursive(pools):
  if len(pools) > 2:
    pools[0] = product(pools[0], pools[1])
    del pools[1]
    return cartesian_recursive(pools)
  else:
    pools[0] = product(pools[0], pools[1])
    del pools[1]
    return pools
def product(x, y):
  return [xx + [yy] if isinstance(xx, list) else [xx] + [yy] for xx in x for yy in y]

Enfoque Lambda

def cartesian_reduct(pools):
  return reduce(lambda x,y: product(x,y) , pools)
weiyixie
fuente
En "Enfoque iterativo", por qué el resultado se declara como resultado = [[]] Sé que es list_of_list pero, en general, incluso si declaramos list_of_list usamos [] y no [[]]
Sachin S
Soy un novato en términos de soluciones Pythonic. ¿Podría usted o algún transeúnte escribir la comprensión de la lista en el "enfoque iterativo" en bucles separados?
Johnny Boy
4

Enfoque recursivo:

def rec_cart(start, array, partial, results):
  if len(partial) == len(array):
    results.append(partial)
    return 

  for element in array[start]:
    rec_cart(start+1, array, partial+[element], results)

rec_res = []
some_lists = [[1, 2, 3], ['a', 'b'], [4, 5]]  
rec_cart(0, some_lists, [], rec_res)
print(rec_res)

Enfoque iterativo:

def itr_cart(array):
  results = [[]]
  for i in range(len(array)):
    temp = []
    for res in results:
      for element in array[i]:
        temp.append(res+[element])
    results = temp

  return results

some_lists = [[1, 2, 3], ['a', 'b'], [4, 5]]  
itr_res = itr_cart(some_lists)
print(itr_res)
Jai
fuente
3

Una modificación menor a la solución del generador recursivo anterior en sabor variadic:

def product_args(*args):
    if args:
        for a in args[0]:
            for prod in product_args(*args[1:]) if args[1:] else ((),):
                yield (a,) + prod

Y, por supuesto, un contenedor que hace que funcione exactamente igual que esa solución:

def product2(ar_list):
    """
    >>> list(product(()))
    [()]
    >>> list(product2(()))
    []
    """
    return product_args(*ar_list)

con una compensación : verifica si la recursión debe romperse en cada bucle externo, y una ganancia : no hay rendimiento en una llamada vacía, por ejemplo product(()), lo que supongo que sería semánticamente más correcto (ver el doctest).

Con respecto a la comprensión de la lista: la definición matemática se aplica a un número arbitrario de argumentos, mientras que la comprensión de la lista solo puede tratar con un número conocido de ellos.

Mike Lu
fuente
2

Solo para agregar un poco a lo que ya se ha dicho: si usa sympy, puede usar símbolos en lugar de cadenas, lo que los hace matemáticamente útiles.

import itertools
import sympy

x, y = sympy.symbols('x y')

somelist = [[x,y], [1,2,3], [4,5]]
somelist2 = [[1,2], [1,2,3], [4,5]]

for element in itertools.product(*somelist):
  print element

Sobre sympy .

Tyler Heers
fuente
1

Creo que esto funciona:

def cartesian_product(L):  
   if L:
       return {(a,) + b for a in L[0] 
                        for b in cartesian_product(L[1:])}
   else:
       return {()}
Richard Samuelson
fuente
0

Enfoque de Stonehenge:

def giveAllLists(a, t):
    if (t + 1 == len(a)):
        x = []
        for i in a[t]:
            p = [i]
            x.append(p)
        return x
    x = []

    out = giveAllLists(a, t + 1)
    for i in a[t]:

        for j in range(len(out)):
            p = [i]
            for oz in out[j]:
                p.append(oz)
            x.append(p)
    return x

xx= [[1,2,3],[22,34,'se'],['k']]
print(giveAllLists(xx, 0))

salida:

[[1, 22, 'k'], [1, 34, 'k'], [1, 'se', 'k'], [2, 22, 'k'], [2, 34, 'k'], [2, 'se', 'k'], [3, 22, 'k'], [3, 34, 'k'], [3, 'se', 'k']]
Sina
fuente