Я предполагаю, что это - академический вопрос, но второй результат не имеет смысла мне. Разве это не должно быть так же полностью пусто как первое? Каково объяснение для этого поведения?
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) ] # [()]
Обновления
Спасибо за все ответы - очень информативный.
Обсуждение Википедии Декартова произведения Nullary обеспечивает категорический оператор:
Декартово пересечение никаких множеств... является одноэлементным набором, содержащим пустой кортеж.
И вот некоторый код, который можно использовать для работы через проницательный ответ от 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
С математической точки зрения продукт без элементов не должен давать нейтральный элемент операции продукт , каким бы он ни был.
Например, для целых чисел нейтральный элемент умножения равен 1 , поскольку 1 ⋅ a = a для всех целых чисел a . Таким образом, пустое произведение целых чисел должно быть 1 . При реализации функции Python, которая возвращает произведение списка чисел, это происходит естественным образом:
def iproduct(lst):
result = 1
for i in lst:
result *= i
return result
Для правильного вычисления этого алгоритма результат
необходимо инициализировать с помощью 1
. Это приводит к возвращению значения 1
, когда функция вызывается для пустого списка.
Это возвращаемое значение также очень разумно для функции. При хорошей функции продукта не имеет значения, если вы сначала объедините два списка, а затем создадите продукт из элементов, или если вы сначала создадите продукт обоих отдельных списков, а затем умножите результаты:
iproduct(xs + ys) == iproduct(xs) * iproduct(ys)
If xs
или ys
пусто, что работает, только если iproduct ([]) == 1
.
Теперь более сложный product ()
на итераторах. Здесь также, с математической точки зрения, product ([])
должен возвращать нейтральный элемент этой операции, каким бы он ни был. Это не []
, поскольку product ([], xs) == []
, в то время как для нейтральных элементов должно выполняться product ([], xs) == xs
.Однако оказывается, что [()]
также не является нейтральным элементом:
>>> list(product([()], [1,2,3]))
[((), 1), ((), 2), ((), 3)]
На самом деле, product ()
на самом деле не очень хороший математический продукт. , поскольку это уравнение выше не выполняется:
product(*(xs + ys)) != product(product(*xs), product(*ys))
Каждое приложение product генерирует дополнительный уровень кортежей, и нет никакого способа обойти это, поэтому не может быть даже реального нейтрального элемента. [()]
подходит довольно близко, он не добавляет и не удаляет какие-либо элементы, он просто добавляет пустой кортеж к каждому из них.
[()]
на самом деле будет нейтральным элементом этой слегка адаптированной функции продукта, которая работает только со списками кортежей, но не добавляет дополнительные уровни кортежей в каждом приложении:
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 ()
без необходимости вводить специальный случай для пустой ввод.
Как уже указывал @sth, такое поведение правильно с математической точки зрения. Все, в чем вам действительно нужно убедиться, - это то, что список (itertools.product ())
должен содержать ровно один элемент, поскольку, как только вы знаете, что ясно, каким должен быть этот элемент: он должен быть (для согласованности ) кортеж длины 0, и есть только один из них.
Но количество элементов itertools.product (l1, l2, l3, ...)
должно быть просто произведением длин l1
, l2
, 13
, .... Таким образом, количество элементов 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!