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

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)

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

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

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

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

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

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


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)

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)

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.


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 .


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 {()}

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']]
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.