Рассчитать N-ую комбинацию мультимножеств (с повторениями) только на основе индекса
Как я могу рассчитать N-й комбо только на основе его индекса. Должны быть (n+k-1)!/(K!(N-1)!) Комбинации с повторениями.
with n=2, k=5 you get:
0|{0,0,0,0,0}
1|{0,0,0,0,1}
2|{0,0,0,1,1}
3|{0,0,1,1,1}
4|{0,1,1,1,1}
5|{1,1,1,1,1}
Таким образом, black_magic_function(3) должен создать {0,0,1,1,1}.
Это будет входить в шейдер GPU, поэтому я хочу, чтобы каждая рабочая группа / поток могла выяснить свое подмножество перестановок без необходимости хранить последовательность глобально.
with n=3, k=5 you get:
i=0, {0,0,0,0,0}
i=1, {0,0,0,0,1}
i=2, {0,0,0,0,2}
i=3, {0,0,0,1,1}
i=4, {0,0,0,1,2}
i=5, {0,0,0,2,2}
i=6, {0,0,1,1,1}
i=7, {0,0,1,1,2}
i=8, {0,0,1,2,2}
i=9, {0,0,2,2,2}
i=10, {0,1,1,1,1}
i=11, {0,1,1,1,2}
i=12, {0,1,1,2,2}
i=13, {0,1,2,2,2}
i=14, {0,2,2,2,2}
i=15, {1,1,1,1,1}
i=16, {1,1,1,1,2}
i=17, {1,1,1,2,2}
i=18, {1,1,2,2,2}
i=19, {1,2,2,2,2}
i=20, {2,2,2,2,2}
Алгоритм его генерации можно рассматривать как MBnext_multicombination
на http://www.martinbroadhurst.com/combinatorial-algorithms.html
Обновить:
Поэтому я подумал, что я бы заменил биномиальный коэффициент в треугольнике Паскаля на (n+k-1)!/(k!(n-1)!)
чтобы увидеть, как это выглядит.
(* Mathematica code to display pascal and other triangle *)
t1 = Table[Binomial[n, k], {n, 0, 8}, {k, 0, n}];
t2 = Table[(n + k - 1)!/(k! (n - 1)!), {n, 0, 8}, {k, 0, n}];
(*display*)
{Row[#, "\t"]} & /@ t1 // Grid
{Row[#, "\t"]} & /@ t2 // Grid
T1:
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
1 7 21 35 35 21 7 1
1 8 28 56 70 56 28 8 1
T2:
Indeterminate
1 1
1 2 3
1 3 6 10
1 4 10 20 35
1 5 15 35 70 126
1 6 21 56 126 252 462
1 7 28 84 210 462 924 1716
1 8 36 120 330 792 1716 3432 6435
Сравнивая с n=3,k=5
консольный вывод в начале этого поста: третья диагональ {3,6,10,15,21,28,36}
дает индекс каждой точки пролонгации {0,0,0,1,1} -> {0,0,1,1,1} -> {0,1,1,1,1}
и т. д. И диагональ слева от нее показывает, сколько значений содержится в предыдущем блоке (diagonal[2][i] == diagonal[3][i] - diagonal[3][i-1])
). И если вы прочитаете 5-ю строку пирамиды по горизонтали, вы получите максимальное количество комбинаций для увеличения значений N в (n+k-1)!/(k!(n-1)!)
где K=5
,
Вероятно, есть способ использовать эту информацию, чтобы определить точную комбинацию для произвольного индекса, не перечисляя весь набор, но я не уверен, нужно ли мне заходить так далеко. Первоначальная проблема заключалась в том, чтобы просто разложить полное комбо-пространство на равные подмножества, которые могут быть сгенерированы локально и работать параллельно с графическим процессором. Таким образом, вышеприведенный треугольник дает нам начальный индекс каждого блока, из которого тривиально может быть получена комбинация, и все ее последующие элементы постепенно нумеруются. Это также дает нам размер блока и сколько у нас всего комбинаций. Таким образом, теперь возникает проблема упаковки, состоящей в том, как распределить блоки неравномерного размера в группы с одинаковой рабочей нагрузкой на количество потоков X.
3 ответа
Смотрите пример по адресу: https://en.wikipedia.org/wiki/Combinatorial_number_system
Просто замените биномиальный коэффициент на (n+k-1)!/(k!(n-1)!)
,
Если предположить, n=3,k=5
Допустим, мы хотим вычислить 19-ю комбинацию (id=19
).
id=0, {0,0,0,0,0}
id=1, {0,0,0,0,1}
id=2, {0,0,0,0,2}
...
id=16, {1,1,1,1,2}
id=17, {1,1,1,2,2}
id=18, {1,1,2,2,2}
id=19, {1,2,2,2,2}
id=20, {2,2,2,2,2}
Результат, который мы ищем {1,2,2,2,2}
,
Изучая наш треугольник "Т2": n=3,k=5
указывает на 21
, являясь пятым числом (сверху вниз) третьей диагонали (слева направо).
Indeterminate
1 1
1 2 3
1 3 6 10
1 4 10 20 35
1 5 15 35 70 126
1 6 21 56 126 252 462
1 7 28 84 210 462 924 1716
1 8 36 120 330 792 1716 3432 6435
Нам нужно найти наибольшее число в этом ряду (по горизонтали, а не по диагонали), которое не превышает нашего id=19
значение. Так что двигаясь влево от 21
мы приходим к 6
(эта операция выполняется largest
функция ниже). поскольку 6
2-й номер в этом ряду соответствует n==2
(или же g[2,5] == 6
из кода ниже).
Теперь, когда мы нашли 5-е число в комбинации, мы поднимаемся на пол в пирамиде, так что k-1=4
, Мы также вычитаем 6
мы столкнулись ниже от id
, так id=19-6=13
, Повторяя весь процесс, мы находим 5
(n==2
снова) быть наибольшим числом меньше 13
в этом ряду.
Следующий: 13-5=8
Самый большой 4
в этом ряду (n==2
еще раз).
Следующий: 8-4=4
Самый большой 3
в этом ряду (n==2
еще раз).
Следующий: 4-3=1
Самый большой 1
в этом ряду (n==1
)
Таким образом, собирая индексы на каждом этапе, мы получаем {1,2,2,2,2}
Следующий код Mathematica делает работу:
g[n_, k_] := (n + k - 1)!/(k! (n - 1)!)
largest[i_, nn_, kk_] := With[
{x = g[nn, kk]},
If[x > i, largest[i, nn-1, kk], {nn,x}]
]
id2combo[id_, n_, 0] := {}
id2combo[id_, n_, k_] := Module[
{val, offset},
{val, offset} = largest[id, n, k];
Append[id2combo[id-offset, n, k-1], val]
]
Обновление: порядок создания комбинаций MBnext_multicombination
не соответствует id2combo
Так что я не думаю, что они были лексикографическими. Функция ниже генерирует их в том же порядке, что и id2combo
и соответствует порядку математики Sort[]
функция в списке списков.
void next_combo(unsigned int *ar, unsigned int n, unsigned int k)
{
unsigned int i, lowest_i;
for (i=lowest_i=0; i < k; ++i)
lowest_i = (ar[i] < ar[lowest_i]) ? i : lowest_i;
++ar[lowest_i];
i = (ar[lowest_i] >= n)
? 0 // 0 -> all combinations have been exhausted, reset to first combination.
: lowest_i+1; // _ -> base incremented. digits to the right of it are now zero.
for (; i<k; ++i)
ar[i] = 0;
}
Вот реализация комбинаторной системы счисления , которая обрабатывает комбинации с повторением и без него (т. е. мультимножество) и может дополнительно производить лексикографическое упорядочение.
/// Combinatorial number system encoder/decoder
/// https://en.wikipedia.org/wiki/Combinatorial_number_system
struct CNS(
/// Type for packed representation
P,
/// Type for one position in unpacked representation
U,
/// Number of positions in unpacked representation
size_t N,
/// Cardinality (maximum value plus one) of one position in unpacked representation
U unpackedCard,
/// Produce lexicographic ordering?
bool lexicographic,
/// Are repetitions representable? (multiset support)
bool multiset,
)
{
static:
/// Cardinality (maximum value plus one) of the packed representation
static if (multiset)
enum P packedCard = multisetCoefficient(unpackedCard, N);
else
enum P packedCard = binomialCoefficient(unpackedCard, N);
alias Index = P;
private P summand(U value, Index i)
{
static if (lexicographic)
{
value = cast(U)(unpackedCard-1 - value);
i = cast(Index)(N-1 - i);
}
static if (multiset)
value += i;
return binomialCoefficient(value, i + 1);
}
P pack(U[N] values)
{
P packed = 0;
foreach (Index i, value; values)
{
static if (!multiset)
assert(i == 0 || value > values[i-1]);
else
assert(i == 0 || value >= values[i-1]);
packed += summand(value, i);
}
static if (lexicographic)
packed = packedCard-1 - packed;
return packed;
}
U[N] unpack(P packed)
{
static if (lexicographic)
packed = packedCard-1 - packed;
void unpackOne(Index i, ref U r)
{
bool checkValue(U value, U nextValue)
{
if (summand(nextValue, i) > packed)
{
r = value;
packed -= summand(value, i);
return true;
}
return false;
}
// TODO optimize: (rolling product / binary search / precomputed tables)
// TODO optimize: don't check below N-i
static if (lexicographic)
{
foreach_reverse (U value; 0 .. unpackedCard)
if (checkValue(value, cast(U)(value - 1)))
break;
}
else
{
foreach (U value; 0 .. unpackedCard)
if (checkValue(value, cast(U)(value + 1)))
break;
}
}
U[N] values;
static if (lexicographic)
foreach (Index i, ref r; values)
unpackOne(i, r);
else
foreach_reverse (Index i, ref r; values)
unpackOne(i, r);
return values;
}
}
Полный код: https://gist.github.com/CyberShadow/67da819b78c5fd16d266a1a3b4154203
Я сделал предварительный анализ по этой проблеме. Прежде чем говорить о неэффективном решении, которое я нашел, позвольте мне дать вам ссылку на статью, которую я написал о том, как преобразовать k-индексы (или комбинации) в ранг или лексикографический индекс в комбинации, связанные с биномиальным коэффициентом:
http://tablizingthebinomialcoeff.wordpress.com/
Я начал так же, пытаясь решить эту проблему. Я придумал следующий код, который использует один цикл для каждого значения k в формуле (n+k-1)!/ K!(N-1)! когда k = 5. Как написано, этот код будет генерировать все комбинации для случая n, выберите 5:
private static void GetCombos(int nElements)
{
// This code shows how to generate all the k-indexes or combinations for any number of elements when k = 5.
int k1, k2, k3, k4, k5;
int n = nElements;
int i = 0;
for (k5 = 0; k5 < n; k5++)
{
for (k4 = k5; k4 < n; k4++)
{
for (k3 = k4; k3 < n; k3++)
{
for (k2 = k3; k2 < n; k2++)
{
for (k1 = k2; k1 < n; k1++)
{
Console.WriteLine("i = " + i.ToString() + ", " + k5.ToString() + " " + k4.ToString() +
" " + k3.ToString() + " " + k2.ToString() + " " + k1.ToString() + " ");
i++;
}
}
}
}
}
}
Результат этого метода:
i = 0, 0 0 0 0 0
i = 1, 0 0 0 0 1
i = 2, 0 0 0 0 2
i = 3, 0 0 0 1 1
i = 4, 0 0 0 1 2
i = 5, 0 0 0 2 2
i = 6, 0 0 1 1 1
i = 7, 0 0 1 1 2
i = 8, 0 0 1 2 2
i = 9, 0 0 2 2 2
i = 10, 0 1 1 1 1
i = 11, 0 1 1 1 2
i = 12, 0 1 1 2 2
i = 13, 0 1 2 2 2
i = 14, 0 2 2 2 2
i = 15, 1 1 1 1 1
i = 16, 1 1 1 1 2
i = 17, 1 1 1 2 2
i = 18, 1 1 2 2 2
i = 19, 1 2 2 2 2
i = 20, 2 2 2 2 2
Это те же значения, которые вы указали в отредактированном ответе. Я также попробовал это с 4 выберите 5, и, похоже, он генерирует правильные комбинации.
Я написал это на C#, но вы должны быть в состоянии использовать его с другими языками, такими как C/C++, Java или Python без слишком большого количества правок.
Одна идея для несколько неэффективного решения состоит в том, чтобы модифицировать GetCombos, чтобы также принимать k в качестве входных данных. Поскольку k ограничено 6, тогда можно было бы провести проверку на k. Таким образом, код для генерации всех возможных комбинаций для случая n select k будет выглядеть следующим образом:
private static void GetCombos(int k, int nElements)
{
// This code shows how to generate all the k-indexes or combinations for any n choose k, where k <= 6.
//
int k1, k2, k3, k4, k5, k6;
int n = nElements;
int i = 0;
if (k == 6)
{
for (k6 = 0; k6 < n; k6++)
{
for (k5 = 0; k5 < n; k5++)
{
for (k4 = k5; k4 < n; k4++)
{
for (k3 = k4; k3 < n; k3++)
{
for (k2 = k3; k2 < n; k2++)
{
for (k1 = k2; k1 < n; k1++)
{
Console.WriteLine("i = " + i.ToString() + ", " + k5.ToString() + " " + k4.ToString() +
" " + k3.ToString() + " " + k2.ToString() + " " + k1.ToString() + " ");
i++;
}
}
}
}
}
}
}
else if (k == 5)
{
for (k5 = 0; k5 < n; k5++)
{
for (k4 = k5; k4 < n; k4++)
{
for (k3 = k4; k3 < n; k3++)
{
for (k2 = k3; k2 < n; k2++)
{
for (k1 = k2; k1 < n; k1++)
{
Console.WriteLine("i = " + i.ToString() + ", " + k5.ToString() + " " + k4.ToString() +
" " + k3.ToString() + " " + k2.ToString() + " " + k1.ToString() + " ");
i++;
}
}
}
}
}
}
else if (k == 4)
{
// One less loop than k = 5.
}
else if (k == 3)
{
// One less loop than k = 4.
}
else if (k == 2)
{
// One less loop than k = 3.
}
else
{
// k = 1 - error?
}
}
Итак, теперь у нас есть метод, который будет генерировать все интересующие комбинации. Но проблема состоит в том, чтобы получить конкретную комбинацию из лексикографического порядка или ранга того, где эта комбинация находится внутри множества. Таким образом, это может быть выполнено простым подсчетом и возвращением правильной комбинации, когда она достигает указанного значения. Таким образом, чтобы учесть это, в метод необходимо добавить дополнительный параметр, представляющий ранг. Итак, новая функция для этого выглядит так:
private static int[] GetComboOfRank(int k, int nElements, int Rank)
{
// Gets the combination for the rank using the formula (n+k-1)!/k!(n-1)! where k <= 6.
int k1, k2, k3, k4, k5, k6;
int n = nElements;
int i = 0;
int[] ReturnArray = new int[k];
if (k == 6)
{
for (k6 = 0; k6 < n; k6++)
{
for (k5 = 0; k5 < n; k5++)
{
for (k4 = k5; k4 < n; k4++)
{
for (k3 = k4; k3 < n; k3++)
{
for (k2 = k3; k2 < n; k2++)
{
for (k1 = k2; k1 < n; k1++)
{
if (i == Rank)
{
ReturnArray[0] = k1;
ReturnArray[1] = k2;
ReturnArray[2] = k3;
ReturnArray[3] = k4;
ReturnArray[4] = k5;
ReturnArray[5] = k6;
return ReturnArray;
}
i++;
}
}
}
}
}
}
}
else if (k == 5)
{
for (k5 = 0; k5 < n; k5++)
{
for (k4 = k5; k4 < n; k4++)
{
for (k3 = k4; k3 < n; k3++)
{
for (k2 = k3; k2 < n; k2++)
{
for (k1 = k2; k1 < n; k1++)
{
if (i == Rank)
{
ReturnArray[0] = k1;
ReturnArray[1] = k2;
ReturnArray[2] = k3;
ReturnArray[3] = k4;
ReturnArray[4] = k5;
return ReturnArray;
}
i++;
}
}
}
}
}
}
else if (k == 4)
{
// Same code as in the other cases, but with one less loop than k = 5.
}
else if (k == 3)
{
// Same code as in the other cases, but with one less loop than k = 4.
}
else if (k == 2)
{
// Same code as in the other cases, but with one less loop than k = 3.
}
else
{
// k = 1 - error?
}
// Should not ever get here. If we do - it is some sort of error.
throw ("GetComboOfRank - did not find rank");
}
ReturnArray возвращает комбинацию, связанную с рангом. Итак, этот код должен работать для вас. Тем не менее, это будет намного медленнее, чем то, что может быть достигнуто, если будет выполнен поиск в таблице. Проблема с 300 выбрать 6 заключается в том, что:
300 choose 6 = 305! / (6!(299!) = 305*304*303*302*301*300 / 6! = 1,064,089,721,800
Это, вероятно, слишком много данных для хранения в памяти. Итак, если бы вы могли получить n до 20 с помощью предварительной обработки, то вы бы смотрели в общей сложности:
20 choose 6 = 25! / (6!(19!)) = 25*24*23*22*21*20 / 6! = 177,100
20 choose 5 = 24! / (5!(19!)) = 24*23*22*21,20 / 5! = 42,504
20 choose 4 = 23! / (4!(19!)) = 23*22*21*20 / 4! = 8,855
20 choose 3 = 22! / (3!(19!)) = 22*21*20 / 3! = 1,540
20 choose 2 = 21! / (2!(19!)) = 22*21 / 2! = 231
=======
230,230
Если для каждого значения комбинации используется один байт, то общее количество байтов, используемых для хранения таблицы (через зубчатый массив или, возможно, 5 отдельных таблиц) в памяти, может быть рассчитано как:
177,100 * 6 = 1,062,600
42,504 * 5 = 212,520
8,855 * 4 = 35,420
1,540 * 3 = 4,620
231 * 2 = 462
=========
1,315,622
Это зависит от целевой машины и объема доступной памяти, но 1 315 622 байта - это не так много памяти, когда сегодня на многих машинах имеется гигабайт доступной памяти.