N-ая комбинация
Есть ли прямой способ получить N-ю комбинацию упорядоченного множества всех комбинаций nCr?
Пример: у меня есть четыре элемента: [6, 4, 2, 1]. Все возможные комбинации, взяв по три за раз, будут:
[[6, 4, 2], [6, 4, 1], [6, 2, 1], [4, 2, 1]].
Есть ли алгоритм, который даст мне, например. 3-й ответ [6, 2, 1] в упорядоченном результирующем наборе без перечисления всех предыдущих ответов?
Ответы
Ответ 1
Обратите внимание, что вы можете сгенерировать последовательность путем рекурсивного генерации всех комбинаций с первым элементом, а затем всех комбинаций без. В обоих рекурсивных случаях вы удаляете первый элемент, чтобы получить все комбинации из n-1 элементов. В Python:
def combination(l, r):
if r == 0:
yield []
elif len(l) == r:
yield l
else:
for c in (combination(l[1:], r-1)):
yield l[0:1]+c
for c in (combination(l[1:], r)):
yield c
Каждый раз, когда вы генерируете последовательность, делая такой выбор, вы можете рекурсивно генерировать элемент k th, подсчитывая, сколько элементов выбирает выбор и сравнивает счетчик с k. Если k меньше, чем счет, вы делаете этот выбор. В противном случае вычитайте счетчик и повторите для других возможных вариантов, которые вы могли бы сделать в этой точке. Если всегда есть выбор b
, вы можете просмотреть это как создание числа в базе b
. Техника по-прежнему работает, если количество вариантов варьируется. В псевдокоде (когда все варианты всегда доступны):
kth(k, choicePoints)
if choicePoints is empty
return empty list
for each choice in head of choicePoints:
if k < size of choice
return choice and kth(k, tail of choicePoints)
else
k -= size of choice
signal exception: k is out-of-bounds
Это дает вам индекс на основе 0. Если вы хотите 1 на основе, измените сравнение на k <= size of choice
.
Сложная часть (и то, что неуказано в псевдокоде) заключается в том, что размер выбора зависит от предыдущих вариантов. Обратите внимание, что псевдокод может использоваться для решения более общего случая, чем проблема.
Для этой конкретной проблемы есть два варианта (b
= 2), а размер 1-го выбора (т.е. включая элемент 1 st) задается n-1суб > С <суб > г-1суб > . Здесь одна реализация (которая требует подходящего nCr
):
def kthCombination(k, l, r):
if r == 0:
return []
elif len(l) == r:
return l
else:
i=nCr(len(l)-1, r-1)
if k < i:
return l[0:1] + kthCombination(k, l[1:], r-1)
else:
return kthCombination(k-i, l[1:], r)
Если вы измените порядок выбора, вы измените порядок последовательности.
def reverseKthCombination(k, l, r):
if r == 0:
return []
elif len(l) == r:
return l
else:
i=nCr(len(l)-1, r)
if k < i:
return reverseKthCombination(k, l[1:], r)
else:
return l[0:1] + reverseKthCombination(k-i, l[1:], r-1)
Положив его на использование:
>>> l = [6, 4, 2, 1]
>>> [kthCombination(k, [6, 4, 2, 1], 3) for k in range(nCr(len(l), 3)) ]
[[6, 4, 2], [6, 4, 1], [6, 2, 1], [4, 2, 1]]
>>> powOf2s=[2**i for i in range(4,-1,-1)]
>>> [sum(kthCombination(k, powOf2s, 3)) for k in range(nCr(len(powOf2s), 3))]
[28, 26, 25, 22, 21, 19, 14, 13, 11, 7]
>>> [sum(reverseKthCombination(k, powOf2s, 3)) for k in range(nCr(len(powOf2s), 3))]
[7, 11, 13, 14, 19, 21, 22, 25, 26, 28]
Ответ 2
Один из способов сделать это - использовать свойства бит. Это все еще требует некоторого перечисления, но вам не нужно будет перечислять каждый набор.
В вашем примере у вас есть 4 номера в вашем наборе. Поэтому, если вы создали все возможные комбинации из 4 чисел, вы можете перечислить их следующим образом:
{6, 4, 2, 1}
0000 - {(no numbers in set)}
0001 - {1}
0010 - {2}
0011 - {2, 1}
...
1111 - {6, 4, 2, 1}
Посмотрите, как каждый "бит" соответствует "соответствует ли это число в вашем наборе"? Мы видим здесь, что существует 16 возможностей (2 ^ 4).
Итак, теперь мы можем пройти и найти все возможности, включающие только 3 бита. Это сообщит нам все комбинации "3", которые существуют:
0111 - {4, 2, 1}
1011 - {6, 2, 1}
1101 - {6, 4, 1}
1110 - {6, 4, 2}
И позволяет переписать каждый из наших двоичных значений в виде десятичных значений:
0111 = 7
1011 = 11
1101 = 13
1110 = 14
Теперь, когда мы это сделали - ну, вы сказали, что хотите перечислить "3-й". Поэтому давайте посмотрим на 3-е по величине число: 11. Который имеет бит 1011, который соответствует... {6, 2, 1}
Круто!
В принципе, вы можете использовать ту же концепцию для любого набора. Итак, теперь все, что мы сделали, переводит проблему из "перечисления всех множеств" в "перечисление всех целых чисел". Это может быть намного проще для вашей проблемы.
Ответ 3
- TL;DR? Просто прокрутите до самого низа для моего окончательного решения .
Я наткнулся на этот вопрос, пока я искал методы, чтобы получить index указанную комбинацию , если бы она была в лексикографически отсортированной и наоборот, для выбора объектов из некоторого потенциально очень сильного большого набора объектов и не мог найти много на последнем (обратное к вашему проблема не настолько неуловима).
Так как я также решил (как я думал) вашу конкретную проблему, прежде чем я подумал, что отправлю свои решения и здесь, но и здесь.
**
EDIT: Мое требование - это то, что было вашим требованием - я видел ответы, и рекурсия мысли была в порядке. Хорошо, теперь, через шесть долгих лет, у вас это есть; просто прокрутите вниз.
**
Для вашего требования, как (я думал, это было), поставленного в вопросе, это сделает работу просто прекрасной:
def iterCombinations(n, k):
if k==1:
for i in range(n):
yield [i]
return
result = []
for a in range(k-1, n):
for e in iterCombinations(n, k-1):
if e[-1] == a:
break
yield e + [a]
Затем вы можете найти элемент в коллекции, упорядоченной в порядке убывания (или использовать некоторую эквивалентную методологию сравнения), поэтому для рассматриваемого случая:
>>> itemsDescending = [6,4,2,1]
>>> for c in iterCombinations(4, 3):
... [itemsDescending[i] for i in c]
...
[6, 4, 2]
[6, 4, 1]
[6, 2, 1]
[4, 2, 1]
Это также возможно прямо из коробки в Python:
>>> import itertools
>>> for c in itertools.combinations(itemsDescending, 3):
... c
...
(6, 4, 2)
(6, 4, 1)
(6, 2, 1)
(4, 2, 1)
Вот что я сделал для своего требования (и действительно для вашего!) алгоритма нерекурсивного, который не создает или не перемещает упорядоченный список для любого направления, а скорее использует простой, но эффективная нерекурсивная реализация n C r, выберите (n, k):
def choose(n, k):
'''Returns the number of ways to choose k items from n items'''
reflect = n - k
if k > reflect:
if k > n:
return 0
k = reflect
if k == 0:
return 1
for nMinusIPlus1, i in zip(range(n - 1, n - k, -1), range(2, k + 1)):
n = n * nMinusIPlus1 // i
return n
Чтобы получить комбинацию в некотором (нулевом) индексе в отсортированном списке вперед:
def iterCombination(index, n, k):
'''Yields the items of the single combination that would be at the provided
(0-based) index in a lexicographically sorted list of combinations of choices
of k items from n items [0,n), given the combinations were sorted in
descending order. Yields in descending order.
'''
if index < 0 or index >= choose(n, k):
return
n -= 1
for i in range(k):
while choose(n, k) > index:
n -= 1
yield n
index -= choose(n, k)
n -= 1
k -= 1
Чтобы получить индекс (на основе нуля), в котором какая-либо комбинация будет находиться в упорядоченном списке reverse:
def indexOfCombination(combination):
'''Returns the (0-based) index the given combination would have if it were in
a reverse-lexicographically sorted list of combinations of choices of
len(combination) items from any possible number of items (given the
combination length and maximum value)
- combination must already be in descending order,
and it items drawn from the set [0,n).
'''
result = 0
for i, a in enumerate(combination):
result += choose(a, i + 1)
return result
Это излишне для вашего примера (но теперь я понимаю, что это был всего лишь пример); так будет выглядеть каждый индекс по очереди:
def exampleUseCase(itemsDescending=[6,4,2,1], k=3):
n = len(itemsDescending)
print("index -> combination -> and back again:")
for i in range(choose(n, k)):
c = [itemsDescending[j] for j in iterCombination(i, n, k)][-1::-1]
index = indexOfCombination([itemsDescending.index(v) for v in c])
print("{0} -> {1} -> {2}".format(i, c, index))
>>> exampleUseCase()
index -> combination -> and back again:
0 -> [6, 4, 2] -> 0
1 -> [6, 4, 1] -> 1
2 -> [6, 2, 1] -> 2
3 -> [4, 2, 1] -> 3
Это может найти индекс, заданный некоторым длинным списком, или вернуть комбинацию в каком-либо астрономическом индексе в мигание глаза, например:
>>> choose(2016, 37)
9617597205504126094112265433349923026485628526002095715212972063686138242753600
>>> list(iterCombination(_-1, 2016, 37))
[2015, 2014, 2013, 2012, 2011, 2010, 2009, 2008, 2007, 2006, 2005, 2004, 2003,
2002, 2001, 2000, 1999, 1998, 1997, 1996, 1995, 1994, 1993, 1992, 1991, 1990, 1989,
1988, 1987, 1986, 1985, 1984, 1983, 1982, 1981, 1980, 1979]
или, поскольку это было самым последним и могло быть быстрым из-за отражения в select (n, k), здесь один справа от середины, и он кажется столь же быстрым...
>>> choose(2016, 37)//2
4808798602752063047056132716674961513242814263001047857606486031843069121376800
>>> list(iterCombination(_, 2016, 37))
[1978, 1973, 1921, 1908, 1825, 1775, 1747, 1635, 1613, 1598, 1529, 1528, 1521,
1445, 1393, 1251, 1247, 1229, 1204, 1198, 922, 901, 794, 699, 685, 633, 619, 598,
469, 456, 374, 368, 357, 219, 149, 93, 71]
В этом последнем примере пауза для размышлений в течение секунды секунды, но не так ли?
>>> import random
>>> rSet = set(random.randint(0, 10000000) for i in range(900))
>>> len(rSet)
900
>>> rList = sorted(rSet, reverse=True)
>>> combinations.indexOfCombination(rList)
61536587905102303838316048492163850175478325236595592744487336325506086930974887
88085020093159925576117511028315621934208381981476407812702689774826510322023536
58905845549371069786639595263444239118366962232872361362581506476113967993096033
00541202874946853699568596881200225925266331936183173583581021914595163799417151
30442624813775945054888304722079206982972852037480516813527237183254850056012217
59834465303543702263588008387352235149083914737690225710105023486226582087736870
38383323140972279867697434315252036074490127510158752080225274972225311906715033
86851377357968649982293794242170046400174118714525559851836064661141086690326842
25236658978135989907667078625869419802333512020715700514133380517628637151215549
05922388534567108671308819960483147825031620798631811671493891643972220604919591
22785587505280326638477135315176731640100473359830821781905546117103137944239120
34912084544221250309244925308316352643060056100719194985568284049903555621750881
39419639825279398618630525081169688672242833238889454445237928356800414839702024
66807635358129606994342005075585962080795273287472139515994244684088406544976674
84183671032002497594936116837768233617073949894918741875863985858049825755901232
89317507965160689287607868119414903299382093412911433254998227245783454244894604
83654290108678890682359278892580855226717964180806265176337132759167920384512456
91624558534942279041452960272707049107641475225516294235268581475735143470692000
78400891862852130481822509803019636619427631175355448729708451565341764545325720
79277290914349746541071731127111532099038538549697091038496002102703737347343739
96398832832674081286904287066696046621691978697914823322322650123025472624927566
99891468668052668317066769517155581261265629289158798073055495539590686279250097
27295943276536772955923599217742543093669565147228386873469711200278811335649924
13587219640724942441913695193417732608127949738209466313175361161142601108707568
19470026889319648128790363676253707359290547393198350533094409863254710237344552
47692325209744353688541868412075798500629908908768438513508959321262250985142709
19794478379412756202638771417821781240327337108495689300616872374578607430951230
96908870723878513999404242546015617238957825116802801618973562178005776911079790
22026655573872019955677676783191505879571719659770550759779880002320421606755826
75809722478174545846409923210824885805972611279030267270741509747224602604003738
30411365119180944456819762167312738395140461035991994771968906979578667047734952
21981545694935313345331923300019842406900689401417602004228459137311983483386802
30352489602769346000257761959413965109940729263098747702427952104316612809425394
85037536245288888254374135695390839718978818689595231708490351927063849922772653
26064826999661128817511630298712833048667406916285156973335575847429111697259113
53969532522640227276562651123634766230804871160471143157687290382053412295542343
14022687833967461351170188107671919648640149202504369991478703293224727284508796
06843631262345918398240286430644564444566815901074110609701319038586170760771099
41252989796265436701638358088345892387619172572763571929093224171759199798290520
71975442996399826830220944004118266689537930602427572308646745061258472912222347
18088442198837834539211242627770833874751143136048704550494404981971932449150098
52555927020553995188323691320225317096340687798498057634440618188905647503384292
79493920419695886724506109053220167190536026635080266763647744881063220423654648
36855624855494077960732944499038847158715263413026604773216510801253044020991845
89652657529729792772055725210165026891724511953666038764273616212464901231675592
46950937136633665320781952510620087284589083139308516989522633786063418913473703
96532777760440118656525488729217328376766171004246127636983612583177565603918697
15557602015171235214344399010185766876727226408494760175957535995025356361689144
85181975631986409708533731043231896096597038345028523539733981468056497208027899
6245509252811753667386001506195
Однако возврат из этого индекса в комбинацию из 900-select-10,000,000, который он представляет с предыдущей реализацией, будет очень медленным (поскольку он просто вычитает один из n на каждой итерации).
Для таких больших списков комбинаций мы можем вместо этого выполнять двоичный поиск пространства, а накладные расходы, которые мы добавляем, означают, что он будет немного медленнее для небольших списков комбинаций:
def iterCombination(index, n, k):
'''Yields the items of the single combination that would be at the provided
(0-based) index in a lexicographically sorted list of combinations of choices
of k items from n items [0,n), given the combinations were sorted in
descending order. Yields in descending order.
'''
if index < 0 or n < k or n < 1 or k < 1 or choose(n, k) <= index:
return
for i in range(k, 0, -1):
d = (n - i) // 2 or 1
n -= d
while 1:
nCi = choose(n, i)
while nCi > index:
d = d // 2 or 1
n -= d
nCi = choose(n, i)
if d == 1:
break
n += d
d //= 2
n -= d
yield n
index -= nCi
Из этого можно заметить, что все вызовы choose
имеют термины, которые отменяют, если мы отменяем все, мы получаем гораздо более быструю реализацию и что, я думаю...
Оптимальная функция для этой задачи
def iterCombination(index, n, k):
'''Yields the items of the single combination that would be at the provided
(0-based) index in a lexicographically sorted list of combinations of choices
of k items from n items [0,n), given the combinations were sorted in
descending order. Yields in descending order.
'''
nCk = 1
for nMinusI, iPlus1 in zip(range(n, n - k, -1), range(1, k + 1)):
nCk *= nMinusI
nCk //= iPlus1
curIndex = nCk
for k in range(k, 0, -1):
nCk *= k
nCk //= n
while curIndex - nCk > index:
curIndex -= nCk
nCk *= (n - k)
nCk -= nCk % k
n -= 1
nCk //= n
n -= 1
yield n
Последнее напоминание о том, что для варианта использования вопроса можно сделать примерно следующее:
def combinationAt(index, itemsDescending, k):
return [itemsDescending[i] for i in
list(iterCombination(index, len(itemsDescending), k))[-1::-1]]
>>> itemsDescending = [6,4,2,1]
>>> numberOfItemsBeingChosen = 3
>>> zeroBasedIndexWanted = 1
>>> combinationAt(zeroBasedIndexWanted, itemsDescending, numberOfItemsBeingChosen)
[6, 4, 1]
Ответ 4
просто грубый эскиз:
упорядочивайте числа в верхнюю треугольную матрицу кортежей:
A(n-1,n-1)
Aij = [i+1, j-1]
если вы сначала пересекаете строку матрицы, вы получите комбинации в порядке возрастания для двух элементов. Чтобы обобщить на три элемента, подумайте о своих матричных строках как о другой треугольной матрице, а не о векторе. Это вроде создает угол куба.
По крайней мере, так я бы подошел к проблеме
Позвольте мне пояснить это, вам не нужно хранить матрицу, вам нужно будет вычислить индекс.
Позвольте мне разобраться с примером размеров, который вы в принципе могли бы расширить до 20 измерений (бухгалтерия может быть жестокой).
ij = (i*i + i)/2 + j // ij is also the combination number
(i,j) = decompose(ij) // from ij one can recover i,j components
I = i // actual first index
J = j + 1 // actual second index
этот двумерный пример работает для любого числа n, и вам не нужно указывать перестановки.
Ответ 5
Из Python 3.6 рецепты itertools:
def nth_combination(iterable, r, index):
'Equivalent to list(combinations(iterable, r))[index]'
pool = tuple(iterable)
n = len(pool)
if r < 0 or r > n:
raise ValueError
c = 1
k = min(r, n-r)
for i in range(1, k+1):
c = c * (n - k + i) // i
if index < 0:
index += c
if index < 0 or index >= c:
raise IndexError
result = []
while r:
c, n, r = c*r//n, n-1, r-1
while index >= c:
index -= c
c, n = c*(n-r)//n, n-1
result.append(pool[-1-n])
return tuple(result)
На практике:
iterable, r, index = [6, 4, 2, 1], 3, 2
nth_combination(iterable, r, index)
# (6, 2, 1)
В качестве альтернативы, как упоминалось в docstring:
import itertools as it
list(it.combinations(iterable, r))[index]
# (6, 2, 1)