Ottieni il prodotto cartesiano di una serie di elenchi?


317

Come posso ottenere il prodotto cartesiano (ogni possibile combinazione di valori) da un gruppo di elenchi?

Ingresso:

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

Uscita desiderata:

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

24
essere consapevoli del fatto che "ogni possibile combinazione" non è esattamente la stessa del "prodotto cartesiano", poiché nei prodotti cartesiani sono consentiti duplicati.
Trittico,

7
Esiste una versione non duplicata del prodotto cartesiano?
KJW,

16
@KJW Sì,set(cartesian product)
NoBugs,

5
Non dovrebbero esserci duplicati in un prodotto cartesiano, a meno che gli elenchi di input non contengano duplicati stessi. Se non si desidera duplicati nel prodotto cartesiano, utilizzare set(inputlist)su tutti gli elenchi di input. Non sul risultato.
CamilB,

@Triptych cosa? La definizione standard di un prodotto cartesiano è un insieme. Perché così tante persone votano?
PascalIv

Risposte:


378

itertools.product

Disponibile da Python 2.6.

import itertools

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

Che è lo stesso di

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

22
Volevo solo aggiungere il carattere '*' è necessario se usi le liste variabili come fornito dal PO.
Brian Buck,

1
@jaska: product()genera nitems_in_a_list ** nlistselementi nel risultato ( reduce(mul, map(len, somelists))). Non vi è motivo di ritenere che il rendimento di un singolo elemento non sia O(nlists)(ammortizzato), ovvero la complessità temporale sia la stessa dei cicli nidificati semplicifor , ad es. Per l'input nella domanda :,nlists=3 numero totale di elementi nel risultato :,3*2*2 e ogni elemento ha nlistselementi ( 3in questo caso).
jfs

2
A che cosa servono le *someliste? Che cosa fa?
Vineet Kumar Doshi,

6
@VineetKumarDoshi: qui viene utilizzato per annullare la compressione di un elenco in più argomenti alla chiamata della funzione. Maggiori informazioni qui: stackoverflow.com/questions/36901/…
Moberg,

4
Nota: questo funziona solo se ogni elenco contiene almeno 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)
>>>

38

Per Python 2.5 e precedenti:

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

Ecco una versione ricorsiva di product()(solo un'illustrazione):

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

Esempio:

>>> 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 versione ricorsiva non funziona se alcuni argssono iteratori.
jfs,

20

con itertools.product :

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

6
A che cosa servono le *someliste?
Vineet Kumar Doshi,

@VineetKumarDoshi "product (somelists)" è un prodotto cartesiano tra le liste secondarie in un modo in cui Python ottiene prima "[1, 2, 3]" come elemento e quindi ottiene altri elementi dopo il prossimo comman e che è l'interruzione di linea, quindi il primo prodotto il termine è ([1, 2, 3],), simile per il secondo ([4, 5],) e quindi "[([1, 2, 3],), ([4, 5],), ( [6, 7],)] " . Se vuoi ottenere un prodotto cartesiano tra elementi all'interno delle tuple, devi dire a Python con Asterisk della struttura delle tuple. Per il dizionario, si utilizza **. Più qui .
hhh,

19

Vorrei usare la comprensione dell'elenco:

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

1
Mi piace molto questa soluzione usando la comprensione dell'elenco. Non so perché non sia più votato, è così semplice.
llekn,

20
@llekn perché il codice sembra essere stato fissato al numero di liste
Bằng Rikimaru

11

Ecco un generatore ricorsivo, che non memorizza alcun elenco temporaneo

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

Produzione:

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

1
Sono comunque archiviati nello stack.
Quentin Pradet,

@QuentinPradet vuoi dire che un generatore come def f(): while True: yield 1continuerà ad aumentare le dimensioni dello stack mentre lo attraversiamo?
Anurag Uniyal

@QuentinPradet sì, ma anche in questo caso solo lo stack necessario per la massima profondità, non l'intero elenco, quindi in questo caso stack di 3
Anurag Uniyal

È vero, scusa. Un benchmark potrebbe essere interessante. :)
Quentin Pradet,

11

In Python 2.6 e versioni successive puoi usare 'itertools.product`. Nelle versioni precedenti di Python è possibile utilizzare il seguente codice (quasi - vedi documentazione) equivalente dalla documentazione , almeno come punto di partenza:

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)

Il risultato di entrambi è un iteratore, quindi se hai davvero bisogno di un elenco per ulteriori elaborazioni, usa list(result).


Secondo la documentazione, l'implementazione effettiva di itertools.product NON genera risultati intermedi, che potrebbero essere costosi. L'uso di questa tecnica potrebbe sfuggire di mano abbastanza rapidamente per elenchi di dimensioni moderate.
Trittico,

4
posso solo indirizzare l'OP alla documentazione, non leggerlo per lui.

1
Il codice della documentazione ha lo scopo di dimostrare ciò che fa la funzione del prodotto, non come soluzione alternativa per le versioni precedenti di Python.
Trittico

9

Sebbene ci siano già molte risposte, vorrei condividere alcuni dei miei pensieri:

Approccio iterativo

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

Approccio ricorsivo

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]

Approccio lambda

def cartesian_reduct(pools):
  return reduce(lambda x,y: product(x,y) , pools)

In "Approccio Iterativo", perché il risultato è dichiarato come risultato = [[]] So che è list_of_list ma in generale anche se abbiamo dichiarato list_of_list usiamo [] e non [[]]
Sachin S

Sono un po 'un novellino in termini di soluzioni Pythonic. Scriveresti tu o qualche passante la comprensione della lista nell '"approccio iterativo" in loop separati?
Johnny Boy,

4

Approccio ricorsivo:

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)

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

3

Una piccola modifica alla soluzione del generatore ricorsivo sopra in sapore variadico:

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

E ovviamente un wrapper che lo fa funzionare esattamente come quella soluzione:

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

con un compromesso : controlla se la ricorsione dovrebbe rompersi su ciascun circuito esterno e un guadagno : nessun rendimento su chiamata vuota, ad esempio product(()), suppongo che sarebbe semanticamente più corretto (vedi il doctest).

Per quanto riguarda la comprensione dell'elenco: la definizione matematica si applica a un numero arbitrario di argomenti, mentre la comprensione dell'elenco potrebbe occuparsi solo di un numero noto di essi.


2

Solo per aggiungere un po 'a ciò che è già stato detto: se usi sympy, puoi usare simboli piuttosto che stringhe che li rendono matematicamente utili.

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

A proposito di Sympy .


1

Credo che funzioni:

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

0

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

produzione:

[[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']]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.