Cómo obtener el producto cartesiano de múltiples listas

6 minutos de lectura

Avatar de usuario de ʞɔıu
ʞɔıu

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

Por ejemplo, dado

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

¿Cómo consigo esto?

[(1, 'a', 4), (1, 'a', 5), (1, 'b', 4), (1, 'b', 5), (2, 'a', 4), (2, 'a', 5), ...]

Una aplicación común de esta técnica es evitar bucles profundamente anidados. Consulte Evitar bucles for anidados para obtener un duplicado más específico. De manera similar, esta técnica podría usarse para “explotar” un diccionario con valores de lista; consulte Combinar permutaciones de diccionarios de Python en una lista de diccionarios.

Si desea un producto cartesiano de lo mismo lista consigo mismo varias veces, itertools.product puede manejar eso con elegancia. Ver Operación en cada par de elementos en una lista o ¿Cómo puedo obtener “permutaciones con repeticiones” de una lista (producto cartesiano de una lista consigo misma)?.

Muchas personas que ya conocen itertools.product lucha con el hecho de que espera argumentos separados para cada secuencia de entrada, en lugar de, por ejemplo, una lista de listas. La respuesta aceptada muestra cómo manejar esto con *. Sin embargo, el uso de * aquí para desempaquetar argumentos es fundamentalmente no diferente desde cualquier otro momento en que se use en una llamada de función. Consulte Expansión de tuplas en argumentos para este tema (y utilícelo en su lugar para cerrar preguntas duplicadas, según corresponda).

  • tenga en cuenta que ‘todas las combinaciones posibles’ no es lo mismo que ‘producto cartesiano’, ya que en los productos cartesianos se permiten duplicados.

    – Kenan Banks

    10 de febrero de 2009 a las 20:08

  • ¿Existe una versión no duplicada del producto cartesiano?

    – KJW

    13 de noviembre de 2013 a las 5:32

  • @KJW Sí, set(cartesian product)

    – Sin errores

    12 de febrero de 2015 a las 7:04

  • 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, utilice set(inputlist) sobre todas sus listas de entrada. No en el resultado.

    – CamilB

    24 de agosto de 2017 a las 8:39

  • Matemáticamente, un producto cartesiano es un conjunto, por lo que un producto cartesiano no no contienen duplicados. Por otro lado, itertools.product tendrá duplicados en la salida si las entradas tienen duplicados. Entonces itertools.product no es estrictamente hablando el producto cartesiano, a menos que envuelva las entradas en setcomo lo menciona @CamilB.

    –Cameron Bieganek

    8 dic 2020 a las 22:56

Avatar de usuario de Kenan Banks
Bancos de Kenan

Usar itertools.productque ha estado disponible desde Python 2.6.

import itertools

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

Esto es lo mismo que:

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

  • Solo quería agregar el carácter ‘*’ si usa la variable somelists según lo proporcionado por el OP.

    – brian dólar

    13 de enero de 2011 a las 22:51

  • @jaska: product() genera nitems_in_a_list ** nlists elementos en el resultado (reduce(mul, map(len, somelists))). No hay razón para creer que ceder un solo elemento no es O(nlists) (amortizado) es decir, la complejidad del tiempo es la misma que para el anidado simple for-bucles, por ejemplo, para la entrada en la pregunta: nlists=3número total de elementos en el resultado: 3*2*2y cada elemento tiene nlists elementos (3 en este caso).

    – jfs

    14 de agosto de 2015 a las 22:08

  • Cuál es el uso de * antes de algunas listas? ¿Qué hace?

    – Vineet Kumar Doshi

    25 de agosto de 2015 a las 9:04

  • @VineetKumarDoshi: Aquí se usa para descomponer una lista en múltiples argumentos para la llamada a la función. Lea más aquí: stackoverflow.com/questions/36901/…

    – Moberg

    15 de septiembre de 2015 a las 6:20

  • Solo un detalle, pero ten en cuenta que itertools.product() también puede manejar generadores, y no solo objetos tipo lista.

    – normanio

    5 de diciembre de 2018 a las 23:18

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

avatar de usuario de jfs
jfs

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

  • La versión recursiva no funciona si algunos de args son iteradores.

    – jfs

    10 de febrero de 2009 a las 21:43

Yo usaría la lista de comprensión:

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

Avatar de usuario de SilentGhost
Fantasma silencioso

con itertools.producto:

import itertools
result = list(itertools.product(*somelists))

  • Cuál es el uso de * antes de algunas listas?

    – Vineet Kumar Doshi

    25 de agosto de 2015 a las 9:04

  • @VineetKumarDoshi “producto(algunas listas)” es un producto cartesiano entre las sublistas de manera que Python obtiene primero “[1, 2, 3]” como un elemento y luego obtiene otro elemento después del siguiente comando y ese es el salto de línea, por lo que el primer término del producto es ([1, 2, 3],), similarmente para el segundo ([4, 5],) y entonces “[([1, 2, 3],), ([4, 5],), ([6, 7],)]”. Si desea obtener un producto cartesiano entre elementos dentro de las tuplas, debe informar a Python con Asterisk sobre la estructura de la tupla. Para el diccionario, usa **. Más aquí.

    – hhh

    15 de febrero de 2016 a las 23:13

Avatar de usuario de Anurag Uniyal
Anurag Uniyal

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

Producción:

[(1, 3, 5), (1, 3, 6), (1, 4, 5), (1, 4, 6), (2, 3, 5), (2, 3, 6), (2, 4, 5), (2, 4, 6)]

  • Cuál es el uso de * antes de algunas listas?

    – Vineet Kumar Doshi

    25 de agosto de 2015 a las 9:04

  • @VineetKumarDoshi “producto(algunas listas)” es un producto cartesiano entre las sublistas de manera que Python obtiene primero “[1, 2, 3]” como un elemento y luego obtiene otro elemento después del siguiente comando y ese es el salto de línea, por lo que el primer término del producto es ([1, 2, 3],), similarmente para el segundo ([4, 5],) y entonces “[([1, 2, 3],), ([4, 5],), ([6, 7],)]”. Si desea obtener un producto cartesiano entre elementos dentro de las tuplas, debe informar a Python con Asterisk sobre la estructura de la tupla. Para el diccionario, usa **. Más aquí.

    – hhh

    15 de febrero de 2016 a las 23:13

En Python 2.6 y superior, puede usar ‘itertools.product`. En versiones anteriores de Python, puede usar el siguiente equivalente (casi, consulte la documentación) código de la documentaciónal 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 un procesamiento posterior, use list(result).

  • 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 las listas de tamaño moderado.

    – Kenan Banks

    10 de febrero de 2009 a las 20:05

  • Solo puedo señalar el OP a la documentación, no leerlo por él.

    usuario3850

    10 de febrero de 2009 a las 20:19

  • El código de la documentación está destinado a demostrar lo que hace la función del producto, no como una solución para versiones anteriores de Python.

    – Kenan Banks

    10 de marzo de 2009 a las 21:07

¿Ha sido útil esta solución?