Что должен дать itertools.product (), когда предоставлен пустой список? - PullRequest
9 голосов
/ 01 июля 2010

Я думаю, это академический вопрос, но второй результат не имеет смысла для меня.Разве он не должен быть таким же пустым, как первый?В чем причина такого поведения?

from itertools import product

one_empty = [ [1,2], [] ]
all_empty = []

print [ t for t in product(*one_empty) ]  # []
print [ t for t in product(*all_empty) ]  # [()]

Обновления

Спасибо за все ответы - очень информативно.

Обсуждение Википедии о Нулевое декартово произведение дает однозначное утверждение:

Декартово произведение без множеств ... - это одноэлементное множество, содержащее пустой кортеж.* А вот код, который вы можете использовать для проработки проницательного ответа от sth :

from itertools import product

def tproduct(*xss):
    return ( sum(rs, ()) for rs in product(*xss) )

def tup(x):
    return (x,)

xs = [ [1, 2],     [3, 4, 5]       ]
ys = [ ['a', 'b'], ['c', 'd', 'e'] ]

txs = [ map(tup, x) for x in xs ]  # [[(1,), (2,)], [(3,), (4,), (5,)]]
tys = [ map(tup, y) for y in ys ]  # [[('a',), ('b',)], [('c',), ('d',), ('e',)]]

a = [ p for p in tproduct( *(txs + tys) )                   ]
b = [ p for p in tproduct( tproduct(*txs), tproduct(*tys) ) ]

assert a == b

Ответы [ 2 ]

10 голосов
/ 01 июля 2010

С математической точки зрения произведение без элементов должно давать нейтральный элемент операции произведение , что бы это ни было.

Например, для целых чисел нейтральный элемент умножения равен 1 , поскольку 1 ⋅ a = a для всех целых чисел a .Таким образом, пустое произведение целых чисел должно быть 1 .При реализации функции python, которая возвращает произведение списка чисел, это происходит естественным образом:

def iproduct(lst):
  result = 1
  for i in lst:
    result *= i
  return result

Для правильного вычисления результата с помощью этого алгоритма result необходимо инициализировать с помощью 1,Это приводит к возвращаемому значению 1, когда функция вызывается в пустом списке.

Это возвращаемое значение также очень разумно для цели функции.С хорошей функцией продукта не должно иметь значения, если вы сначала объедините два списка, а затем создадите продукт из элементов, или если вы сначала создадите продукт из обоих отдельных списков, а затем умножите результаты:

iproduct(xs + ys) == iproduct(xs) * iproduct(ys)

Если xs или ys пусто, это работает, только если iproduct([]) == 1.

Теперь более сложный product() на итераторах.Здесь также, с математической точки зрения, product([]) должен возвращать нейтральный элемент этой операции, какой бы она ни была.Это не [], поскольку product([], xs) == [], в то время как для нейтральных элементов product([], xs) == xs должно удерживаться.Оказывается, однако, что [()] также не является нейтральным элементом:

>>> list(product([()], [1,2,3]))
[((), 1), ((), 2), ((), 3)]

На самом деле, product() на самом деле не очень хороший математический продукт вообще, так как вышеприведенное уравнение не 't hold:

product(*(xs + ys)) != product(product(*xs), product(*ys))

Каждое применение продукта генерирует дополнительный слой кортежей, и нет никакого способа обойти это, поэтому даже не может быть реального нейтрального элемента.[()] подходит довольно близко, хотя он не добавляет и не удаляет какие-либо элементы, он просто добавляет пустой кортеж к каждому.

[()] фактически будет нейтральным элементом этой слегка адаптированной функции продукта, котораяработает только со списками кортежей, но не добавляет дополнительные слои кортежей для каждого приложения:

def tproduct(*xss):
  # the parameters have to be lists of tuples
  return (sum(rs, ()) for rs in product(*xss))

Для этой функции выполняется приведенное выше уравнение продукта:

def tup(x): return (x,)
txs = [map(tup, x) for x in xs]
tys = [map(tup, y) for y in ys]
tproduct(*(txs + tys)) == tproduct(tproduct(*txs), tproduct(*tys))

С дополнительной предварительной обработкойшаг упаковки списков ввода в кортежи, tproduct() дает тот же результат, что и product(), но ведет себя лучше с математической точки зрения.Также его нейтральным элементом является [()],

Так что [()] имеет некоторый смысл в качестве нейтрального элемента этого вида умножения списка.Даже если он не совсем подходит product(), это хороший выбор для этой функции, поскольку, например, он позволяет определять tproduct() без необходимости вводить специальный случай для пустого ввода.

3 голосов
/ 01 июля 2010

Как уже указывалось @sth, это поведение корректно с математической точки зрения. Все, в чем вы действительно должны убедиться, это то, что list(itertools.product()) должен иметь ровно один элемент, поскольку, как только вы поймете, что ясно, каким должен быть этот элемент: он должен быть (для согласованности) кортежом длины 0, и есть только один из них.

Но количество элементов itertools.product(l1, l2, l3, ...) должно быть просто произведением длин l1, l2, l3, .... Таким образом, число элементов itertools.product() должно быть размером с пустой продукт , и нет недостатка в интернет-источниках, которые должны убедить вас, что пустой продукт равен 1.

Я просто хотел указать, что это правильное практическое определение, а также правильное математическое определение; то есть это определение, скорее всего, «просто сработает» в граничных случаях. Например, предположим, что вы хотите сгенерировать все строки длиной n, состоящие из десятичных цифр, с первой цифрой, отличной от нуля. Вы можете сделать что-то вроде:

import itertools

def decimal_strings(n):
    """Generate all digit strings of length n that don't start with 0."""
    for lead_digit in '123456789':
        for tail in itertools.product('0123456789', repeat=n-1):
            yield lead_digit + ''.join(tail)

Что это должно произойти, когда n = 1? Ну, в этом случае вы в конечном итоге звоните itertools.product с пустым продуктом (repeat = 0). Если бы он ничего не возвращал, то тело внутреннего цикла for выше никогда не будет выполнено, поэтому decimal_strings(1) будет пустым итератором; почти наверняка не то, что вы хотите. Но так как itertools.product('0123456789', repeat=0) возвращает один кортеж, вы получите ожидаемый результат:

>>> list(decimal_strings(1))
['1', '2', '3', '4', '5', '6', '7', '8', '9']

(Когда n = 0, конечно, эта функция правильно вызывает ошибку ValueError.)

Итак, короче говоря, математически правильное определение, и чаще всего это не то, что вы хотите. Это определенно не ошибка Python!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...