Holen Sie sich das kartesische Produkt einer Reihe von Listen?

317

Wie kann ich das kartesische Produkt (jede mögliche Wertekombination) aus einer Gruppe von Listen erhalten?

Eingang:

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

Gewünschte Ausgabe:

[(1, 'a', 4), (1, 'a', 5), (1, 'b', 4), (1, 'b', 5), (2, 'a', 4), (2, 'a', 5) ...]
ʞɔıu
quelle
24
Beachten Sie, dass "jede mögliche Kombination" nicht ganz mit "kartesisches Produkt" identisch ist, da bei kartesischen Produkten Duplikate zulässig sind.
Triptychon
7
Gibt es eine nicht doppelte Version des kartesischen Produkts?
KJW
16
@KJW Ja,set(cartesian product)
NoBugs
5
Ein kartesisches Produkt sollte keine Duplikate enthalten, es sei denn, die Eingabelisten enthalten selbst Duplikate. Wenn Sie keine Duplikate im kartesischen Produkt wünschen, verwenden set(inputlist)Sie alle Ihre Eingabelisten. Nicht auf das Ergebnis.
CamilB
@ Triptychon was? Die Standarddefinition eines kartesischen Produkts ist eine Menge. Warum stimmen so viele Menschen zu?
PascalIv

Antworten:

378

itertools.product

Verfügbar ab Python 2.6.

import itertools

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

Welches ist das gleiche wie,

for element in itertools.product([1, 2, 3], ['a', 'b'], [4, 5]):
    print(element)
Triptychon
quelle
22
Das Hinzufügen des Zeichens '*' ist nur erforderlich, wenn Sie die vom OP bereitgestellten variablen Somelisten verwenden.
Brian Buck
1
@jaska: product()generiert nitems_in_a_list ** nlistsElemente im Ergebnis ( reduce(mul, map(len, somelists))). Es gibt keinen Grund zu der Annahme, dass die Ausbeute eines einzelnen Elements nicht O(nlists)(amortisiert) ist, dh die zeitliche Komplexität ist dieselbe wie für einfache verschachtelte forSchleifen, z. B. für die Eingabe in der Frage : nlists=3, Gesamtzahl der Elemente im Ergebnis : 3*2*2, und Jedes Element hat nlistsElemente ( 3in diesem Fall).
JFS
2
Was nützt es *vor Somelisten? Was tut es?
Vineet Kumar Doshi
6
@VineetKumarDoshi: Hier wird es verwendet, um eine Liste in mehrere Argumente für den Funktionsaufruf zu entpacken. Lesen Sie hier mehr: stackoverflow.com/questions/36901/…
Moberg
4
Hinweis: Dies funktioniert nur, wenn jede Liste mindestens ein Element enthält
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
quelle
38

Für Python 2.5 und älter:

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

Hier ist eine rekursive Version von product()(nur eine Illustration):

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

Beispiel:

>>> 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
quelle
Die rekursive Version funktioniert nicht, wenn einige von argsihnen Iteratoren sind.
JFS
20

mit itertools.product :

import itertools
result = list(itertools.product(*somelists))
SilentGhost
quelle
6
Was nützt es *vor Somelisten?
Vineet Kumar Doshi
@VineetKumarDoshi "Produkt (Somelisten)" ist ein kartesisches Produkt zwischen den Unterlisten, so dass Python zuerst "[1, 2, 3]" als Element erhält und dann nach dem nächsten Komman ein anderes Element erhält, und das ist Zeilenumbruch, also das erste Produkt Term ist ([1, 2, 3],), ähnlich für die zweite ([4, 5],) und so "[([1, 2, 3],), ([4, 5],), ( [6, 7],)] " . Wenn Sie ein kartesisches Produkt zwischen Elementen in den Tupeln erhalten möchten, müssen Sie Python mit Asterisk über die Tupelstruktur informieren. Für das Wörterbuch verwenden Sie **. Mehr hier .
hhh
19

Ich würde das Listenverständnis verwenden:

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]]
user1035648
quelle
1
Ich mag diese Lösung mit Listenverständnis sehr. Ich weiß nicht, warum es nicht mehr positiv bewertet wird, es ist so einfach.
llekn
20
@llekn, weil der Code auf die Anzahl der Listen festgelegt zu sein scheint
Bằng Rikimaru
11

Hier ist ein rekursiver Generator, der keine temporären Listen speichert

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

Ausgabe:

[(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
quelle
1
Sie sind jedoch im Stapel gespeichert.
Quentin Pradet
@QuentinPradet meinst du, ein Generator wie def f(): while True: yield 1wird seine Stapelgröße weiter erhöhen, während wir es durchlaufen?
Anurag Uniyal
@ QuentinPradet ja, aber selbst in diesem Fall wird nur der Stapel für maximale Tiefe benötigt, nicht die gesamte Liste, also in diesem Fall Stapel von 3
Anurag Uniyal
Es ist wahr, sorry. Ein Benchmark könnte interessant sein. :)
Quentin Pradet
11

In Python 2.6 und höher können Sie 'itertools.product` verwenden. In älteren Versionen von Python können Sie den folgenden (fast - siehe Dokumentation) äquivalenten Code aus der Dokumentation verwenden , zumindest als Ausgangspunkt:

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)

Das Ergebnis von beiden ist ein Iterator. Wenn Sie also wirklich eine Liste für die weitere Verarbeitung benötigen, verwenden Sie list(result).


quelle
Gemäß der Dokumentation führt die tatsächliche Implementierung von itertools.product NICHT zu Zwischenergebnissen, was teuer sein kann. Die Verwendung dieser Technik kann bei Listen mittlerer Größe recht schnell außer Kontrolle geraten.
Triptychon
4
Ich kann das OP nur auf die Dokumentation verweisen, nicht für ihn lesen.
1
Der Code aus der Dokumentation soll zeigen, was die Produktfunktion tut, und nicht als Problemumgehung für frühere Versionen von Python.
Triptychon
9

Obwohl es bereits viele Antworten gibt, möchte ich einige meiner Gedanken teilen:

Iterativer Ansatz

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

Rekursiver Ansatz

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]

Lambda-Ansatz

def cartesian_reduct(pools):
  return reduce(lambda x,y: product(x,y) , pools)
weiyixie
quelle
In "Iterativer Ansatz", warum wird das Ergebnis als Ergebnis deklariert = [[]] Ich weiß, dass es list_of_list ist, aber im Allgemeinen verwenden wir [] und nicht [[]]
Sachin S
Ich bin ein bisschen neu in Bezug auf Pythonic-Lösungen. Würden Sie oder ein Passant bitte das Listenverständnis im "iterativen Ansatz" in separate Schleifen schreiben?
Johnny Boy
Sehr clevere Lösung! Die Intuition dahinter war sehr cool!
Krish
4

Rekursiver Ansatz:

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)

Iterativer Ansatz:

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
quelle
3

Eine geringfügige Modifikation der obigen rekursiven Generatorlösung in variadischem Geschmack:

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

Und natürlich ein Wrapper, mit dem es genauso funktioniert wie diese Lösung:

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

mit einem Kompromiss : Es wird geprüft, ob die Rekursion bei jeder äußeren Schleife unterbrochen werden soll, und ein Gewinn : Keine Ausbeute bei leerem Aufruf, zproduct(()) was meiner Meinung nach semantisch korrekter wäre (siehe den Test).

Zum Listenverständnis: Die mathematische Definition gilt für eine beliebige Anzahl von Argumenten, während das Listenverständnis nur eine bekannte Anzahl von Argumenten behandeln kann.

Mike Lu
quelle
2

Nur um ein wenig zu dem hinzuzufügen, was bereits gesagt wurde: Wenn Sie Sympy verwenden, können Sie Symbole anstelle von Zeichenfolgen verwenden, was sie mathematisch nützlich macht.

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

Über Sympy .

Tyler Heers
quelle
1

Ich glaube das funktioniert:

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
quelle
0

Stonehenge-Ansatz:

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

Ausgabe:

[[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
quelle