БЫСТРЫЕ уникальные комбинации (из списка с дубликатами) БЕЗ ЛУКОВ
Кажется, что, несмотря на то, что в сети имеется множество алгоритмов и функций для создания уникальных комбинаций любого размера из списка уникальных элементов, в случае списка уникальных элементов нет доступных (т.е. список, содержащий повторения одного значения.)
Вопрос заключается в том, как генерировать ON-THE-FLY в функции генератора все уникальные комбинации из уникального списка безвычислительная дорогостоящая потребность в фильтрации дубликатов?
Теперь, когда есть мотивированный ответ на вопрос, легче дать более ясную информацию о том, чего я ожидаю достичь:
Сначала дайте код, иллюстрирующий, как проверить, является ли комбинация comboB
дубликат другого (comboA
):
comboA = [1,2,2]
comboB = [2,1,2]
print("B is a duplicate of A:", comboA.sort()==comboB.sort())
В данном примере B - это дубликат A, а print() печатает True.
Проблема получения функции генератора, способной обеспечить уникальные комбинации "на лету" в случае неуникального списка, решается здесь: Получение уникальных комбинаций из уникального списка элементов, FASTER?, но предоставленная функция генератора нуждается в поиске и требует памяти, что вызывает проблемы в случае огромного количества комбинаций.
В текущей версии предоставленной функции ответа функция выполняет работу без каких-либо поисков и представляется правильным ответом здесь, НО...
Целью избавления от поиска является ускорение генерации уникальных комбинаций в случае списка с дубликатами.
Я изначально (написав первую версию этого вопроса) ошибочно предположил, что код, который не требует создания набора, используемого для поиска, необходимого для обеспечения уникальности, как ожидается, даст преимущество над кодами, нуждающимися в поиске. Это не тот случай. По крайней мере, не всегда. Код, который до сих пор предоставлял ответ, не использует поисковые запросы, но занимает гораздо больше времени для создания всех комбинаций в случае отсутствия избыточного списка или если в списке имеется только несколько избыточных элементов.
Здесь приведены некоторые тайминги для иллюстрации текущей ситуации:
-----------------
k: 6 len(ls): 48
Combos Used Code Time
---------------------------------------------------------
12271512 len(list(combinations(ls,k))) : 2.036 seconds
12271512 len(list(subbags(ls,k))) : 50.540 seconds
12271512 len(list(uniqueCombinations(ls,k))) : 8.174 seconds
12271512 len(set(combinations(sorted(ls),k))): 7.233 seconds
---------------------------------------------------------
12271512 len(list(combinations(ls,k))) : 2.030 seconds
1 len(list(subbags(ls,k))) : 0.001 seconds
1 len(list(uniqueCombinations(ls,k))) : 3.619 seconds
1 len(set(combinations(sorted(ls),k))): 2.592 seconds
Над таймингами показаны две крайности: нет дубликатов и только дубликатов. Все остальные тайминги находятся между этими двумя.
Моя интерпретация приведенных выше результатов заключается в том, что чистая функция Python (без itertools или других C-скомпилированных модулей) может быть чрезвычайно быстрой, но она может быть намного медленнее в зависимости от того, сколько дубликатов в списке. Таким образом, возможно, нет возможности писать код на С++ для модуля расширения Python.so, обеспечивающего требуемую функциональность.
Ответы
Ответ 1
Вместо последующей обработки/фильтрации вашего вывода вы можете предварительно обработать свой список ввода. Таким образом, вы можете избежать генерации дубликатов в первую очередь. Предварительная обработка включает в себя либо сортировку (или использование collections.Counter
on) ввода. Возможна одна рекурсивная реализация:
def subbags(bag, k):
a = sorted(bag)
n = len(a)
sub = []
def index_of_next_unique_item(i):
j = i + 1
while j < n and a[j] == a[i]:
j += 1
return j
def combinate(i):
if len(sub) == k:
yield tuple(sub)
elif n - i >= k - len(sub):
sub.append(a[i])
yield from combinate(i + 1)
sub.pop()
yield from combinate(index_of_next_unique_item(i))
yield from combinate(0)
bag = [1, 2, 3, 1, 2, 1]
k = 3
i = -1
print(sorted(bag), k)
print('---')
for i, subbag in enumerate(subbags(bag, k)):
print(subbag)
print('---')
print(i + 1)
Вывод:
[1, 1, 1, 2, 2, 3] 3
---
(1, 1, 1)
(1, 1, 2)
(1, 1, 3)
(1, 2, 2)
(1, 2, 3)
(2, 2, 3)
---
6
Требуется некоторое пространство стека для рекурсии, но при этом + сортировка ввода должна использовать существенно меньшее время + память, чем генерация и отбрасывание повторов.
Ответ 2
Современное состояние, вдохновленное первоначально 50, а не 100-кратным бонусами, на данный момент (вместо модуля расширения Python, полностью написанного на C):
Эффективный алгоритм и реализация, который лучше, чем очевидный подход (set + combinations)
в лучшем (и среднем) случае, и конкурирует с ним в худшем случае.
Кажется, что можно выполнить это требование, используя своего рода "подделку, прежде чем вы сделаете это". Современное состояние дел состоит в том, что для решения проблемы получения уникальных комбинаций в случае неуникального списка существует два алгоритма функции генератора. Приведенный ниже алгоритм объединяет оба из них, что становится возможным, поскольку, по-видимому, существует пороговое значение для процента уникальных элементов в списке, которое может использоваться для соответствующего переключения между двумя алгоритмами. Расчет процента уникальности производится с таким крошечным количеством времени вычисления, что он даже явно не проявляется в конечных результатах из-за общей вариации принятого времени.
def iterFastUniqueCombos(lstList, comboSize, percUniqueThresh=60):
lstListSorted = sorted(lstList)
lenListSorted = len(lstListSorted)
percUnique = 100.0 - 100.0*(lenListSorted-len(set(lstListSorted)))/lenListSorted
lstComboCandidate = []
setUniqueCombos = set()
def idxNextUnique(idxItemOfList):
idxNextUniqueCandidate = idxItemOfList + 1
while (
idxNextUniqueCandidate < lenListSorted
and
lstListSorted[idxNextUniqueCandidate] == lstListSorted[idxItemOfList]
): # while
idxNextUniqueCandidate += 1
idxNextUnique = idxNextUniqueCandidate
return idxNextUnique
def combinate(idxItemOfList):
if len(lstComboCandidate) == sizeOfCombo:
yield tuple(lstComboCandidate)
elif lenListSorted - idxItemOfList >= sizeOfCombo - len(lstComboCandidate):
lstComboCandidate.append(lstListSorted[idxItemOfList])
yield from combinate(idxItemOfList + 1)
lstComboCandidate.pop()
yield from combinate(idxNextUnique(idxItemOfList))
if percUnique > percUniqueThresh:
from itertools import combinations
allCombos = combinations(lstListSorted, comboSize)
for comboCandidate in allCombos:
if comboCandidate in setUniqueCombos:
continue
yield comboCandidate
setUniqueCombos.add(comboCandidate)
else:
yield from combinate(0)
#:if/else
#:def iterFastUniqueCombos()
Ниже приведенные тайминги показывают, что приведенная выше функция генератора iterFastUniqueCombos()
обеспечивает явное преимущество
over uniqueCombinations()
в случае, если список содержит менее 60 процентов уникальных элементов и не хуже, чем
на функции (set + combinations)
на основе (set + combinations)
на основе uniqueCombinations()
в противоположном случае, когда он становится намного быстрее, чем iterUniqueCombos()
один (из-за переключения между
вариант (set + combinations)
и (no lookups)
с порогом 60% для количества уникальных элементов в списке):
=========== sizeOfCombo: 6 sizeOfList: 48 noOfUniqueInList 1 percUnique 2
Combos: 12271512 print(len(list(combinations(lst,k)))) : 2.04968 seconds.
Combos: 1 print(len(list( iterUniqueCombos(lst,k)))) : 0.00011 seconds.
Combos: 1 print(len(list( iterFastUniqueCombos(lst,k)))) : 0.00008 seconds.
Combos: 1 print(len(list( uniqueCombinations(lst,k)))) : 3.61812 seconds.
========== sizeOfCombo: 6 sizeOfList: 48 noOfUniqueInList 48 percUnique 100
Combos: 12271512 print(len(list(combinations(lst,k)))) : 1.99383 seconds.
Combos: 12271512 print(len(list( iterUniqueCombos(lst,k)))) : 49.72461 seconds.
Combos: 12271512 print(len(list( iterFastUniqueCombos(lst,k)))) : 8.07997 seconds.
Combos: 12271512 print(len(list( uniqueCombinations(lst,k)))) : 8.11974 seconds.
========== sizeOfCombo: 6 sizeOfList: 48 noOfUniqueInList 27 percUnique 56
Combos: 12271512 print(len(list(combinations(lst,k)))) : 2.02774 seconds.
Combos: 534704 print(len(list( iterUniqueCombos(lst,k)))) : 1.60052 seconds.
Combos: 534704 print(len(list( iterFastUniqueCombos(lst,k)))) : 1.62002 seconds.
Combos: 534704 print(len(list( uniqueCombinations(lst,k)))) : 3.41156 seconds.
========== sizeOfCombo: 6 sizeOfList: 48 noOfUniqueInList 31 percUnique 64
Combos: 12271512 print(len(list(combinations(lst,k)))) : 2.03539 seconds.
Combos: 1114062 print(len(list( iterUniqueCombos(lst,k)))) : 3.49330 seconds.
Combos: 1114062 print(len(list( iterFastUniqueCombos(lst,k)))) : 3.64474 seconds.
Combos: 1114062 print(len(list( uniqueCombinations(lst,k)))) : 3.61857 seconds.