Skip to content

Prodotto cartesiano di liste in Python (itertools.product)

Python

Usa itertools.product() per generare prodotti cartesiani di più elenchi in Python.

In questo articolo vengono descritti i seguenti contenuti.

  • Qual è il prodotto cartesiano
  • Utilizzo di base di itertools.product()
  • Usa lo stesso elenco (iterabile) ricerca:repeat
  • Confronto della velocità con più loop (anello annidato)

Qual è il prodotto cartesiano

Il prodotto cartesiano è l’insieme di tutte le combinazioni di elementi di più insiemi.

Di seguito sono seguiti esempi specifici.

Importa il modulo itertools. È incluso nella libreria standard, quindi non è richiesta alcuna installazione aggiuntiva.pprint viene utilizzato per semplificare la lettura dei risultati.

Passa due liste come argomenti. itertools.product() fornisce un oggetto di tipo itertools.product. itertools.product è un iteratore, quindi il contenuto non viene emesso da print().

import itertools
import pprint

l1 = ['a', 'b', 'c']
l2 = ['X', 'Y', 'Z']

p = itertools.product(l1, l2)

print(p)
# 

print(type(p))
# <class 'itertools.product'>

Puoi ottenere la combinazione di elementi di ciascuna lista come una tupla con il ciclo for. Si noti che non viene emesso nulla se l’iteratore che ha raggiunto la fine viene ruotato di nuovo nel ciclo for.

for v in p:
    print(v)
# ('a', 'X')
# ('a', 'Y')
# ('a', 'Z')
# ('b', 'X')
# ('b', 'Y')
# ('b', 'Z')
# ('c', 'X')
# ('c', 'Y')
# ('c', 'Z')

for v in p:
    print(v)

È anche possibile ottenere ogni elemento separatamente invece di una tupla.

for v1, v2 in itertools.product(l1, l2):
    print(v1, v2)
# a X
# a Y
# a Z
# b X
# b Y
# b Z
# c X
# c Y
# c Z

Il risultato è lo stesso di quando si utilizza loop nidificati (loop multipli).

for v1 in l1:
    for v2 in l2:
        print(v1, v2)
# a X
# a Y
# a Z
# b X
# b Y
# b Z
# c X
# c Y
# c Z

È anche possibile inserire in una lista con tuple come elementi con list().

l_p = list(itertools.product(l1, l2))

pprint.pprint(l_p)
# [('a', 'X'),
#  ('a', 'Y'),
#  ('a', 'Z'),
#  ('b', 'X'),
#  ('b', 'Y'),
#  ('b', 'Z'),
#  ('c', 'X'),
#  ('c', 'Y'),
#  ('c', 'Z')]

print(type(l_p))
# <class 'list'>

print(type(l_p[0]))
# <class 'tuple'>

Puoi passare più iterabili (tuple, list, range, ecc.) a itertools.product().

t = ('one', 'two')
d = {'key1': 'value1', 'key2': 'value2'}
r = range(2)

l_p = list(itertools.product(t, d, r))

pprint.pprint(l_p)
# [('one', 'key1', 0),
#  ('one', 'key1', 1),
#  ('one', 'key2', 0),
#  ('one', 'key2', 1),
#  ('two', 'key1', 0),
#  ('two', 'key1', 1),
#  ('two', 'key2', 0),
#  ('two', 'key2', 1)]

Come puoi vedere dal risultato sopra, quando il dizionario viene assegnato, vengono restituite le chiavi. Se hai bisogno di valori, usa il metodo values(). Vedere il seguente articolo per i dettagli.

Vedere il seguente articolo per ulteriori informazioni sulla gamma().

Usa lo stesso elenco (iterabile) ripetuto: ripeti

È possibile specificare il numero di ripetizioni nell’argomento della parola chiave ripetere. Lo stesso iterabile viene utilizzato per generare un prodotto cartesiano.

l1 = ['a', 'b']

pprint.pprint(list(itertools.product(l1, repeat=3)))
# [('a', 'a', 'a'),
#  ('a', 'a', 'b'),
#  ('a', 'b', 'a'),
#  ('a', 'b', 'b'),
#  ('b', 'a', 'a'),
#  ('b', 'a', 'b'),
#  ('b', 'b', 'a'),
#  ('b', 'b', 'b')]

Vieni nell’esempio seguente senza ripetizione.

pprint.pprint(list(itertools.product(l1, l1, l1)))
# [('a', 'a', 'a'),
#  ('a', 'a', 'b'),
#  ('a', 'b', 'a'),
#  ('a', 'b', 'b'),
#  ('b', 'a', 'a'),
#  ('b', 'a', 'b'),
#  ('b', 'b', 'a'),
#  ('b', 'b', 'b')]

Se sono specificati più iterabili:

l1 = ['a', 'b']
l2 = ['X', 'Y']

pprint.pprint(list(itertools.product(l1, l2, repeat=2)))
# [('a', 'X', 'a', 'X'),
#  ('a', 'X', 'a', 'Y'),
#  ('a', 'X', 'b', 'X'),
#  ('a', 'X', 'b', 'Y'),
#  ('a', 'Y', 'a', 'X'),
#  ('a', 'Y', 'a', 'Y'),
#  ('a', 'Y', 'b', 'X'),
#  ('a', 'Y', 'b', 'Y'),
#  ('b', 'X', 'a', 'X'),
#  ('b', 'X', 'a', 'Y'),
#  ('b', 'X', 'b', 'X'),
#  ('b', 'X', 'b', 'Y'),
#  ('b', 'Y', 'a', 'X'),
#  ('b', 'Y', 'a', 'Y'),
#  ('b', 'Y', 'b', 'X'),
#  ('b', 'Y', 'b', 'Y')]

Uguale al seguente esempio. Nota che è l1, l2, l1, l2 invece di l1, l1, l2, l2.

pprint.pprint(list(itertools.product(l1, l2, l1, l2)))
# [('a', 'X', 'a', 'X'),
#  ('a', 'X', 'a', 'Y'),
#  ('a', 'X', 'b', 'X'),
#  ('a', 'X', 'b', 'Y'),
#  ('a', 'Y', 'a', 'X'),
#  ('a', 'Y', 'a', 'Y'),
#  ('a', 'Y', 'b', 'X'),
#  ('a', 'Y', 'b', 'Y'),
#  ('b', 'X', 'a', 'X'),
#  ('b', 'X', 'a', 'Y'),
#  ('b', 'X', 'b', 'X'),
#  ('b', 'X', 'b', 'Y'),
#  ('b', 'Y', 'a', 'X'),
#  ('b', 'Y', 'a', 'Y'),
#  ('b', 'Y', 'b', 'X'),
#  ('b', 'Y', 'b', 'Y')]

Confronto della velocità con più loop (anello annidato)

Come accennato in precedenza, più cicli (cicli annidati) danno lo stesso risultato di itertools.product().

for v1, v2 in itertools.product(l1, l2):
    print(v1, v2)
# a X
# a Y
# a Z
# b X
# b Y
# b Z
# c X
# c Y
# c Z

for v1 in l1:
    for v2 in l2:
        print(v1, v2)
# a X
# a Y
# a Z
# b X
# b Y
# b Z
# c X
# c Y
# c Z

Come puoi vedere di seguito, itertools.product() è in realtà più lento dei loop nidificati.

I risultati possono variare a seconda del numero di elementi nell’iterabile e del numero di loop, ma in seguito a Domande e risposte su Stack Overflow risponde anche che itertools.product() è più lento.

che segue è il risultato della misurazione del tempo di esecuzione con il comando magico %%timeit in Jupyter Notebook. Nota che non può essere misurato eseguendo come codice Python.

Esempio di doppio loop con 1000 elementi:

Il risultato di itertools.product() è più veloce da decomprimere.

import itertools

A = range(1000)

%%timeit
for x in itertools.product(A, A):
    pass
# 30.8 ms ± 910 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit
for a1, a2 in itertools.product(A, A):
    pass
# 22.8 ms ± 293 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

I nidificati sono più o meno gli stessi (leggermente più veloci) di itertools.product() quando decompressi.

%%timeit
for a1 in A:
    for a2 in A:
        pass
# 22.6 ms ± 345 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

È più veloce non decomprimere si utilizza l’espressione del generatore che è la versione del generatore della comprensione dell’elenco lento, ma è più di itertools.product() o dei cicli nidificati.

%%timeit
for x in ((a1, a2) for a1 in A for a2 in A):
    pass
# 82.2 ms ± 467 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit
for a1, a2 in ((a1, a2) for a1 in A for a2 in A):
    pass
# 91.4 ms ± 276 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

Esempio di calcolo della somma dei prodotti di ciascuna combinazione. Anche in questo caso, è più veloce utilizzare i cicli nidificati rispetto a itertools.product().

%%timeit
v = 0
for a1, a2 in itertools.product(A, A):
    v += a1 * a2
# 98.8 ms ± 579 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit
v = 0
for a1 in A:
    for a2 in A:
        v += a1 * a2
# 95.7 ms ± 4.05 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In questo esempio, passare l’espressione del generatore a sum() è leggermente più veloce.

%%timeit
v = sum(a1 * a2 for a1, a2 in itertools.product(A, A))
# 94 ms ± 2.36 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit
v = sum(a1 * a2 for a1 in A for a2 in A)
# 92.7 ms ± 4.83 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Esempio di triplo loop con 100 elementi:

Anche in questo caso, l’utilizzo di un ciclo for nidificato è il più veloce.

B = range(100)

%%timeit
for x in itertools.product(B, B, B):
    pass
# 31.6 ms ± 725 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit
for b1, b2, b3 in itertools.product(B, B, B):
    pass
# 26.2 ms ± 490 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit
for b1 in B:
    for b2 in B:
        for b3 in B:
            pass
# 12.9 ms ± 176 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%%timeit
for x in ((b1, b2, b3) for b1 in B for b2 in B for b3 in B):
    pass
# 80.9 ms ± 1.27 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit
for b1, b2, b3 in ((b1, b2, b3) for b1 in B for b2 in B for b3 in B):
    pass
# 93.8 ms ± 3.22 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Come descritto sopra, la differenza tra un doppio loop con 1000 elementi e un triplo loop con 100 elementi è di poche decine di millisecondi.