Отримайте декартовий продукт із серії списків?


317

Як я можу отримати декартовий продукт (кожне можливе поєднання значень) із групи списків?

Вхід:

somelists = [
   [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) ...]

24
майте на увазі, що "кожна можлива комбінація" не зовсім така, як "декартовий продукт", оскільки в декартових продуктах дозволені копії.
Триптих

7
Чи існує неповторна версія декартового продукту?
KJW

16
@KJW Так,set(cartesian product)
NoBugs

5
У декартовому продукті не повинно бути дублікатів, якщо тільки вхідні списки не містять дублікатів. Якщо ви не бажаєте дублікатів у декартовому продукті, використовуйте set(inputlist)всі вхідні списки. Не на результат.
CamilB

@Triptych що? Стандартне визначення декартового продукту - це безліч. Чому так багато людей вітає?
PascalIv

Відповіді:


378

itertools.product

Доступний з Python 2.6.

import itertools

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

Що таке, що,

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

22
Просто потрібно додати символ «*», якщо ви використовуєте змінні списки переліку, передбачені ОП.
Брайан Бак

1
@jaska: product()генерує nitems_in_a_list ** nlistsелементи в результаті ( reduce(mul, map(len, somelists))). Немає підстав вважати, що вихід одного елемента не O(nlists)(амортизований), тобто, часова складність така ж, як і для простих вкладених- forпетель, наприклад, для введення запитання:, nlists=3загальна кількість елементів у результаті:, 3*2*2і кожен елемент має nlistsелементи ( 3у цьому випадку).
jfs

2
В чому користь *перед сомелістами? Що це робить?
Vineet Kumar Doshi

6
@VineetKumarDoshi: Тут він використовується для скасування списку на кілька аргументів виклику функції. Детальніше читайте тут: stackoverflow.com/questions/36901/…
Moberg

4
Примітка. Це працює лише в тому випадку, якщо кожен список містить принаймні один елемент
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

Для Python 2.5 та новіших версій:

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

Ось рекурсивна версія product()(лише ілюстрація):

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

Приклад:

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

Рекурсивна версія не працює, якщо деякі з них argsє ітераторами.
jfs

20

з itertools.product :

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

6
В чому користь *перед сомелістами?
Vineet Kumar Doshi

@VineetKumarDoshi "продукт (сомелісти)" - це декартовий продукт між підспісками таким чином, що Python спочатку отримує "[1, 2, 3]" як елемент, а потім отримує інший елемент після наступного учасника, і це linebreak, так що перший продукт термін є ([1, 2, 3],), подібний для другого ([4, 5],) і так "[([1, 2, 3],), ([4, 5],), ( [6, 7],)] " . Якщо ви хочете отримати декартовий продукт між елементами всередині кортежів, вам потрібно повідомити Python з зірочкою про структуру кортежу. Для словника ви використовуєте **. Більше тут .
hhh

19

Я б використав розуміння списку:

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
Мені дуже подобається це рішення, використовуючи розуміння списку. Я не знаю, чому не рекомендується більше, це так просто.
llekn

20
@llekn, оскільки, здається, код зафіксовано до кількості списків
Bằng Rikimaru

11

Ось рекурсивний генератор, який не зберігає жодних тимчасових списків

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

Вихід:

[(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
Хоча вони зберігаються в стеці.
Квентін Прадет

@QuentinPradet Ви маєте на увазі такий генератор, як ми def f(): while True: yield 1будемо продовжувати збільшувати розмір стека, коли ми проходимо його?
Anurag Uniyal

@QuentinPradet Так, але навіть у цьому випадку потрібен лише стек для максимальної глибини, а не весь список, тож у цьому випадку стек 3
Anurag Uniyal

Це правда, вибач. Тест може бути цікавим. :)
Квентін Прадет

11

У Python 2.6 і вище ви можете використовувати "itertools.product". У старих версіях Python ви можете використовувати наступний (майже - див. Документацію) еквівалент код із документації , принаймні, як вихідну точку:

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)

Результат обох - ітератор, тому якщо вам справді потрібен список для подальшої обробки, використовуйте list(result) .


Згідно з документацією, реальна реалізація itertools.product НЕ створює проміжних результатів, які можуть бути дорогими. Використання цієї методики може досить швидко вийти з ладу для списків середнього розміру.
Триптих

4
Я можу лише вказати ОП на документацію, а не читати його.

1
Код з документації призначений для демонстрації того, що робить функція продукту, а не як вирішення попередніх версій Python.
Триптих

9

Хоча відповідей уже багато, я хотів би поділитися своїми думками:

Ітеративний підхід

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

Рекурсивний підхід

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]

Лембдаський підхід

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

У "Ітеративному підході" чому результат оголошується результатом = [[]] Я знаю, що це list_of_list, але взагалі навіть якщо ми оголосили list_of_list, ми використовуємо [], а не [[]]
Sachin S

Я трохи новачок в плані пітонічних рішень. Ви б чи хтось із перехожих просимо написати окремі списки в "ітеративному підході" окремими циклами?
Джонні Хлопчик

4

Рекурсивний підхід:

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)

Ітеративний підхід:

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

Незначна модифікація вищевказаного рекурсивного генераторного рішення в різноманітному ароматі:

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

І звичайно обгортка, яка змушує її працювати точно так само, як це рішення:

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

з одним компромісом : він перевіряє, чи повинна рекурсія перерватися на кожен зовнішній цикл, і одним посиленням : жоден вихід при порожньому виклику, наприклад product(()), який, я вважаю, був би семантично правильнішим (див. документ).

Що стосується розуміння списку: математичне визначення застосовується до довільної кількості аргументів, тоді як розуміння списку може мати справу лише з відомою їх кількістю.


2

Просто додати трохи до вже сказаного: якщо ви використовуєте sympy, ви можете використовувати символи, а не рядки, що робить їх математично корисними.

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

Про симпатію .



0

Підхід Стоунхенджа:

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

вихід:

[[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']]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.