Что должно itertools.product () урожай при предоставлении пустой список?

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

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

10
задан Community 23 May 2017 в 11:53
поделиться

2 ответа

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

Например, для целых чисел нейтральный элемент умножения равен 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 () без необходимости вводить специальный случай для пустой ввод.

10
ответ дан 3 December 2019 в 23:48
поделиться

Как уже указывал @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!

3
ответ дан 3 December 2019 в 23:48
поделиться
Другие вопросы по тегам:

Похожие вопросы: