С теоретической точки зрения самый элегантный способ сделать это, по моему скромному мнению, - получить одиночное случайное число между 0 и n! -1 и для вычисления отображения один в один из {0, 1, …, n!-1}
для всех перестановок (0, 1, 2, …, n-1)
. Пока вы можете использовать (псевдо) генератор случайных чисел, достаточно надежный для получения такого числа без какого-либо существенного смещения, у вас будет достаточно информации для достижения того, что вы хотите, без необходимости использования нескольких других случайных чисел.
При вычислении с плавающими числами двойной точности IEEE754 вы можете ожидать, что ваш генератор случайных чисел предоставит около 15 десятичных знаков. Поскольку у вас есть 15! = 1 307 674 368 000 (с 13 цифрами), вы можете использовать следующие функции с массивами, содержащими до 15 элементов, и предполагать, что не будет существенного смещения с массивами, содержащими до 14 элементов. Если вы работаете с проблемой фиксированного размера, требующей многократного вычисления этой операции тасования, вы можете попробовать следующий код, который может быть быстрее, чем другие коды, поскольку он использует Math.random
только один раз (он включает однако несколько операций копирования).
Следующая функция не будет использоваться, но я все равно дам ее; возвращает индекс заданной перестановки (0, 1, 2, …, n-1)
в соответствии с отображением «один к одному», используемым в этом сообщении (наиболее естественным при перечислении перестановок); он предназначен для работы с 16 элементами:
function permIndex(p) {
var fact = [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800, 479001600, 6227020800, 87178291200, 1307674368000];
var tail = [];
var i;
if (p.length == 0) return 0;
for(i=1;i<(p.length);i++) {
if (p[i] > p[0]) tail.push(p[i]-1);
else tail.push(p[i]);
}
return p[0] * fact[p.length-1] + permIndex(tail);
}
Ответ предыдущей функции (требуется для вашего собственного вопроса) приведен ниже; он предназначен для работы до 16 элементов; он возвращает перестановку порядка n из (0, 1, 2, …, s-1)
:
function permNth(n, s) {
var fact = [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800, 479001600, 6227020800, 87178291200, 1307674368000];
var i, j;
var p = [];
var q = [];
for(i=0;i<s;i++) p.push(i);
for(i=s-1; i>=0; i--) {
j = Math.floor(n / fact[i]);
n -= j*fact[i];
q.push(p[j]);
for(;j<i;j++) p[j]=p[j+1];
}
return q;
}
Теперь, что вы просто хотите:
function shuffle(p) {
var fact = [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800, 479001600, 6227020800, 87178291200, 1307674368000, 20922789888000];
return permNth(Math.floor(Math.random()*fact[p.length]), p.length).map(
function(i) { return p[i]; });
}
Должно работать до 16 элементов с небольшим теоретическим уклоном (хотя и не заметно с практической точки зрения); его можно рассматривать как полностью пригодный для 15 элементов; с массивами, содержащими менее 14 элементов, можно смело полагать, что смещения не будет абсолютно.