Obtenir le produit cartésien d'une série de listes?

317

Comment puis-je obtenir le produit cartésien (toutes les combinaisons possibles de valeurs) à partir d'un groupe de listes?

Contribution:

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

Sortie désirée:

[(1, 'a', 4), (1, 'a', 5), (1, 'b', 4), (1, 'b', 5), (2, 'a', 4), (2, 'a', 5) ...]
ʞɔıu
la source
24
sachez que «chaque combinaison possible» n'est pas tout à fait la même chose que «produit cartésien», car dans les produits cartésiens, les doublons sont autorisés.
Triptyque
7
Existe-t-il une version non dupliquée du produit cartésien?
KJW
16
@KJW Oui,set(cartesian product)
NoBugs
5
Il ne doit pas y avoir de doublons dans un produit cartésien, sauf si les listes d'entrée contiennent elles-mêmes des doublons. Si vous ne voulez pas de doublons dans le produit cartésien, utilisez-le set(inputlist)sur toutes vos listes de saisie. Pas sur le résultat.
CamilB
@Triptych quoi? La définition standard d'un produit cartésien est un ensemble. Pourquoi tant de gens votent-ils?
PascalIv

Réponses:

378

itertools.product

Disponible à partir de Python 2.6.

import itertools

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

C'est la même chose que,

for element in itertools.product([1, 2, 3], ['a', 'b'], [4, 5]):
    print(element)
Triptyque
la source
22
Je voulais juste ajouter le caractère '*' est requis si vous utilisez les listes de variables comme fournies par l'OP.
brian buck
1
@jaska: product()génère des nitems_in_a_list ** nlistséléments dans le résultat (reduce(mul, map(len, somelists)) ). Il n'y a aucune raison de croire que le fait de produire un seul élément n'est pas O(nlists)(amorti), c'est-à-dire que la complexité temporelle est la même que pour les forboucles imbriquées simples, par exemple, pour l'entrée dans la question :,nlists=3 nombre total d'éléments dans le résultat :,3*2*2 et chaque élément a des nlistséléments ( 3dans ce cas).
jfs
2
À quoi sert *avant les listes de diffusion? Qu'est ce que ça fait?
Vineet Kumar Doshi
6
@VineetKumarDoshi: Ici, il est utilisé pour décompresser une liste en plusieurs arguments de l'appel de fonction. En savoir plus ici: stackoverflow.com/questions/36901/…
Moberg
4
Remarque: cela ne fonctionne que si chaque liste contient au moins un élément
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
la source
38

Pour Python 2.5 et versions antérieures:

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

Voici une version récursive de product()(juste une 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])

Exemple:

>>> 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
la source
La version récursive ne fonctionne pas si certains argssont des itérateurs.
jfs
20

avec itertools.product :

import itertools
result = list(itertools.product(*somelists))
SilentGhost
la source
6
À quoi sert *avant les listes de diffusion?
Vineet Kumar Doshi
@VineetKumarDoshi "product (somelists)" est un produit cartésien entre les sous-listes de manière à ce que Python obtienne d'abord "[1, 2, 3]" en tant qu'élément, puis récupère l'autre élément après la prochaine virgule et c'est le saut de ligne donc le premier produit terme est ([1, 2, 3],), similaire pour le second ([4, 5],) et ainsi "[([1, 2, 3],), ([4, 5],), ( [6, 7],)] " . Si vous voulez obtenir un produit cartésien entre les éléments à l'intérieur des tuples, vous devez informer Python avec Asterisk de la structure du tuple. Pour le dictionnaire, vous utilisez **. Plus ici .
hhh
19

J'utiliserais la compréhension de liste:

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
la source
1
J'aime vraiment cette solution en utilisant des listes de compréhension. Je ne sais pas pourquoi n'est pas plus voté, c'est si simple.
llekn
20
@llekn parce que le code semble être fixé sur le nombre de listes
Bằng Rikimaru
11

Voici un générateur récursif, qui ne stocke aucune liste temporaire

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

Production:

[(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
la source
1
Ils sont cependant stockés dans la pile.
Quentin Pradet
@QuentinPradet voulez-vous dire qu'un générateur comme celui- def f(): while True: yield 1ci continuera d'augmenter la taille de sa pile au fur et à mesure?
Anurag Uniyal
@QuentinPradet oui, mais même dans ce cas, seule la pile nécessaire pour la profondeur maximale, pas toute la liste, donc dans ce cas pile de 3
Anurag Uniyal
C'est vrai, désolé. Une référence pourrait être intéressante. :)
Quentin Pradet
11

Dans Python 2.6 et supérieur, vous pouvez utiliser 'itertools.product`. Dans les anciennes versions de Python, vous pouvez utiliser le code équivalent suivant (presque - voir la documentation) de la documentation , au moins comme point de départ:

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)

Le résultat des deux est un itérateur, donc si vous avez vraiment besoin d'une liste pour un traitement ultérieur, utilisez list(result).


la source
Selon la documentation, l'implémentation itertools.product réelle ne génère PAS de résultats intermédiaires, ce qui pourrait être coûteux. L'utilisation de cette technique pourrait rapidement devenir incontrôlable pour les listes de taille moyenne.
Triptyque
4
je ne peux que pointer l'OP vers la documentation, pas la lire pour lui.
1
Le code de la documentation est destiné à montrer ce que fait la fonction produit, et non comme solution de contournement pour les versions antérieures de Python.
Triptyque
9

Bien qu'il existe déjà de nombreuses réponses, je voudrais partager certaines de mes réflexions:

Approche itérative

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

Approche récursive

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]

Approche Lambda

def cartesian_reduct(pools):
  return reduce(lambda x,y: product(x,y) , pools)
weiyixie
la source
Dans "Approche itérative", pourquoi le résultat est-il déclaré comme résultat = [[]] Je sais que c'est list_of_list mais en général même si nous avons déclaré list_of_list nous utilisons [] et non [[]]
Sachin S
Je suis un peu novice en termes de solutions Pythonic. Est-ce que vous ou un passant pourriez écrire la compréhension de la liste dans "l'approche itérative" dans des boucles séparées?
Johnny Boy
4

Approche récursive:

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)

Approche itérative:

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)
I have
la source
3

Une modification mineure à la solution de générateur récursif ci-dessus de saveur variadique:

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

Et bien sûr, un wrapper qui le fait fonctionner exactement de la même manière que cette solution:

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

avec un compromis : il vérifie si la récursivité doit se briser sur chaque boucle externe, et un gain : pas de rendement lors d'un appel vide, par exemple product(()), ce qui, je suppose, serait sémantiquement plus correct (voir le doctorat).

Concernant la compréhension de liste: la définition mathématique s'applique à un nombre arbitraire d'arguments, tandis que la compréhension de liste ne peut en traiter qu'un nombre connu.

Mike Lu
la source
2

Juste pour ajouter un peu à ce qui a déjà été dit: si vous utilisez sympy, vous pouvez utiliser des symboles plutôt que des chaînes, ce qui les rend mathématiquement utiles.

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

À propos de sympy .

Tyler Heers
la source
1

Je crois que cela fonctionne:

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
la source
0

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

production:

[[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
la source