Решите k-комбинацию, входные данные которой могут содержать повторяющиеся элементы, и верните только уникальные комбинации.
например, ввод [0,1,2,2,4]
, k = 2
, результат:
{(0, 1), (2, 4), (1, 2), (0, 4), (1, 4), (0, 2), (2, 2)}
Единственное общее решение, которое я могу придумать, — это использовать set
или map
для извлечения уникальных результатов.
например с питоном
from itertools import combinations
print (set(combinations([0, 1, 2, 2, 4], 2)))
Я могу генерировать комбинации уникальных элементов по порядку (см.: https://stackoverflow.com/a/76048486), но если есть повторяющиеся элементы, то будут повторяющиеся комбинации.
Я читал это: https://math.stackexchange.com/a/554237 Кажется, там говорится, что нет общего решения для получения всех уникальных комбинаций, хотя есть формула для подсчета уникальных комбинаций.
Но я не уверен.
python
на go
:
yield
в python нужно chan
в go, или вам нужно вернуть все комбинации (что может быть огромным для большого ввода).else
Python для for
и while
опускается, когда break
.slicing
в python создаст копию (ссылки?), в то время как нарезка слайса go будет повторно использовать тот же базовый массив (так что в go, если копируемый слайс будет изменен позже, и вы не хотите, чтобы это изменение в копию, то вы должны скопировать/добавить ее в новый фрагмент).@JosephWood Это вопрос algorithm
, и я не вижу подходящего ответа. (Кстати, я совершил ту же ошибку, пометил как дубликат этого и отказался, когда понял, что это не python
вопрос...)
@KellyBundy, кстати, не знал о more_itertools.distinct_combinations
. Я должен проверить это.
@JosephWood Для меня sympy.multiset_combinations
не звучит обычно. Можно ли использовать in в Java или Go?
Использование "set" значительно упрощает задачу, устраняет ненужную сложность, так что это жизнеспособный способ.
@MBo При создании перестановок существует алгоритм, позволяющий избежать дубликатов без набора, поэтому мне интересно, есть ли аналогичный способ для создания комбинации. Использование набора простое, но может потребовать дополнительного времени и места в сложности.
@Eric Усилия по предотвращению дублирования для большого массива могут занять гораздо больше времени. Конечно, это необходимо для оценки баланса. Например, мы можем сортировать элементы, чтобы упростить предотвращение дублирования использования, но сама сортировка сравнима с построением множества.
@MBo В случае перестановки существует алгоритм предотвращения дублирования в природе, без дополнительного времени или пространства, он даже поддерживает порядок. Проверьте мой ответ с кодом: stackoverflow.com/a/75894614 , а алгоритм объясняется здесь: en.wikipedia.org/wiki/… Хотя я не уверен, можно ли добиться того же для комбинации , и вопрос был задан с целью получить ответ или совет.
@ Эрик Да, я знаю об этом алгоритме перестановки. Я хотел сказать, что это имеет смысл, потому что результирующая перестановка включает все повторяющиеся элементы... но только что заметила, что вы также хотите комбинацию (2, 2)
- поэтому установить здесь невозможно. Возможно, я что-то такое, как вам давно нужно, постараюсь поискать.
@MBo Set работает, я использовал set()
в самом вопросе в примере с Python, он работает хорошо. И, я думаю, stackoverflow.com/questions/75887690 это решение для перестановки не включает дублированную перестановку.
@ Эрик Ага, вы используете множество вместо результирующих комбинаций - определенно излишне для больших списков.
@MBo Например, мы можем поместить set внутрь функции и проверить цикл при создании списка возврата. Так что размер набора будет таким же, как и возвращаемый список.
@Eric Установить хранилище, генерировать все комбинации и проверять их непродуктивно. По моей ссылке показан рекурсивный метод, не требующий проверки, он строит только нужные комбинации, а также ответы ниже.
Вот код, который генерирует их по одному в Python.
Я использую итераторы. Перевод от этого был бы самой сложной частью перевода этого на другой язык.
def unique_multisets (multiset, k):
# Find the repetition of each item.
count = {}
for x in multiset:
count[x] = 1 + count.get(x, 0)
# Turn this into (value, count) pairs. The reversed is
# because we will reverse it later in generating the multisets.
pairs = list(reversed(count.items()))
# Calculate a running total.
total = 0
totals = []
for p in pairs:
total += p[1]
totals.append(total)
def recurse (partial, idx):
if k == len(partial):
yield partial
elif idx < 0 or totals[idx] < k - len(partial):
pass # Can't make combinations.
else:
(value, repeat) = pairs[idx]
max_used = min(repeat, k - len(partial))
for i in range(1 + max_used):
partial.append(value)
for i in range(1 + max_used):
partial.pop()
yield from recurse(partial, idx - 1)
yield from recurse([], len(pairs)-1)
for x in unique_multisets([0, 1, 2, 2, 4], 2):
print(x)
Выглядит правильно, интересно, можно ли сделать это iteration
, (позже я попробую после полного понимания). Я считаю, что у каждой рекурсии есть итерация, но не уверен.
@ Эрик Это идет другим путем. Каждую итерацию можно переписать как рекурсию. Но есть рекурсивные функции, которые нельзя переписать как итерацию, стандартным примером является функция Аккермана. Я могу решить это итеративно, но это будет совсем другое решение.
@btilly Это звучит неправильно. Вы имеете в виду какую-то ограниченную «итерацию»?
@KellyBundy, именно так: вы можете итеративно вычислять любую рекурсивную функцию, эмулируя стек вызовов с соответствующим сегментированием точек входа/выхода. Примитивно-рекурсивные функции не требуют этого «хака» — вы можете определить количество циклов перед входом в цикл. Непримитивные рекурсивные функции (например, функция Аккермана) требуют явного стека.
@KellyBundy Я имел в виду «кучу for
петель». С помощью стека вызовов вы можете эмулировать что угодно — в первую очередь именно так мы можем запускать рекурсивные функции.
Хм, "связка петель" пока неясна. Но я сомневаюсь, что @Eric имел в виду то, что ты имеешь в виду.
Согласно stackoverflow.com/questions/931762, в нескольких ответах говорится, что каждую рекурсивную функцию можно записать в виде итеративной.
Вот нерекурсивный перечислитель комбинаций в лексикографическом порядке, оптимизированный для простоты, а не для скорости. Книга Кнута «Искусство компьютерного программирования» почти наверняка имеет оптимизированную по скорости версию.
def combinations(a, k):
a = sorted(a)
while True:
yield a[:k]
for i in range(k - 1, -1, -1):
# a[i + 1 :] is sorted. Sort a[i:].
x = a[i]
j = i + 1
while j < len(a):
if x < a[j]:
break
a[j - 1] = a[j]
j += 1
a[j - 1] = x
# a[j] is the next greatest element after x. If there are enough
# elements after it, rotate them into position.
if j + (k - i) <= len(a):
rotate(a, i, j, j + (k - i))
break
else:
break
def rotate(a, i, j, k):
reverse(a, i, j)
reverse(a, j, k)
reverse(a, i, k)
def reverse(a, i, k):
for j in range((k - i) // 2):
a[i + j], a[(k - 1) - j] = a[(k - 1) - j], a[i + j]
print(*combinations([0, 1, 2, 2, 4], 2))
print(*combinations([0, 1, 2, 2, 4], 3))
print(*combinations([0, 0, 0, 1, 1, 1], 3))
Выход:
[0, 1] [0, 2] [0, 4] [1, 2] [1, 4] [2, 2] [2, 4]
[0, 1, 2] [0, 1, 4] [0, 2, 2] [0, 2, 4] [1, 2, 2] [1, 2, 4] [2, 2, 4]
[0, 0, 0] [0, 0, 1] [0, 1, 1] [1, 1, 1]
Я перевел ваш ответ: go.dev/play/p/RyanyA38Vuc как общую версию. Еще один go-перевод, отправляющий комбинации на канал: go.dev/play/p/P1_ik7w8j-Q, что больше похоже на функцию yield в python.
Ниже приведен алгоритм, который я разработал некоторое время назад для этой задачи:
def multiset_combinations(v, m):
v = sorted(v)
f = [0]
j = 0
# Populating f, a list of expanded indices. E.g. For
#
# v = [2, 2, 33, 33, 33, 40]
#
# f would be:
#
# [0, 0, 1, 1, 1, 2]
for i in range(1, len(v)):
if v[i] != v[i - 1]:
j += 1
f.append(j)
v = list(set(v))
r = [0] * m
n = len(v)
z = f[:m]
idx = [0] * n
p = len(f) - m
last = f[-m:]
last[-1] += 1
# Find the first occurence of each index. E.g. For the
# same example above, idx would be:
#
# [0, 2, 5]
for i in range(n):
idx[i] = f.index(i)
# The main algorithm
while True:
while z[-1] < n:
for j in range(m):
r[j] = v[z[j]]
z[-1] += 1
yield r.copy()
if z == last:
break
for i in range(m - 2, -1, -1):
if z[i] != f[p + i]:
z[i] += 1
for j, k in zip(range(i + 1, m),
range(idx[z[i]] + 1, idx[z[i]] + m)):
z[j] = f[k]
break
Вызов:
print(*multiset_combination([0, 1, 2, 2, 4], 2))
#> [0, 1] [0, 2] [0, 4] [1, 2] [1, 4] [2, 2] [2, 4]
print(*multiset_combination([0, 1, 2, 2, 4], 3))
#> [0, 1, 2] [0, 1, 4] [0, 2, 2] [0, 2, 4] [1, 2, 2] [1, 2, 4] [2, 2, 4]
print(*multiset_combination([0, 0, 0, 1, 1, 1], 3))
#> [0, 0, 0] [0, 0, 1] [0, 1, 1] [1, 1, 1]
Это также довольно эффективно. Он может генерировать 753564
менее чем за секунду:
v1 = [1,
2, 2,
3, 3, 3,
4, 4, 4, 4,
5, 5, 5, 5, 5,
6,
7, 7,
8, 8, 8,
9, 9, 9, 9,
10, 10, 10, 10, 10,
11,
12, 12,
13, 13, 13,
14, 14, 14, 14,
15, 15, 15, 15, 15]
def get_time(v, m):
t1 = time.time()
list(multiset_combinations(v, m))
t2 = time.time()
return t2 - t1
get_time(v1, 10)
#> 0.8702290058135986
Ваша версия Python заканчивается на 1.38 s
, версия из другого ответа stackoverflow.com/a/76064642 заканчивается на 9.93 s
на моем ноутбуке, так что ваша действительно быстрее. Хотя go намного быстрее, чем кажется python.
Я перевел ваш ответ на go
: go.dev/play/p/jdQvr6c5b0g , он заканчивается на 0.12 s
без печати, а мой перевод другого ответа go.dev/play/p/RyanyA38Vuc занимает 0.20 s
на моем ноутбуке. Так что ваш все равно быстрее. Кстати, приведенная выше ссылка не может закончиться в go.dev/play, вероятно, из-за того, что она требует слишком много ресурсов, ее нужно запускать локально.
Я добавил еще один перевод go
, который отправляет комбинации на канал go, который больше похож на yield
в python
: go.dev/play/p/3WivvzV2E0u, теперь его можно запускать онлайн, он занимает 0.15 s
на моем ноутбуке, и он общий. .
Отвечает ли это на ваш вопрос? Как сгенерировать все различные комбинации (где элементы ввода повторяются) в Python (используя Itertools)?