Для игры, реализованной в JavaScript, мне нужно создать случайный список из n
уникальных чисел в диапазоне [0, N)
, где N
может быть больше Number.MAX_SAFE_INTEGER
. Это создает три важных проблемы:
- Стоимость памяти не может быть O (N), так как у нас не будет достаточно оперативной памяти. Это означает, что мы не можем использовать модифицированный алгоритм перемешивания Фишера-Йейтса для получения
n
перемешанных значений из массива N
элементов. (JS массивы ограничены 32-битными индексами, но у нас закончится ОЗУ задолго до этого.) - Время выполнения должно быть как можно ближе к O (n), а не к O (N ). Я ожидаю, что n будет относительно небольшим (<100). </li>
- Большинство математических операций используют значения с плавающей запятой двойной точности и не будут работать с такими большими числами.
Я нашел решение к первым двум задачам в алгоритме отбора проб коллектора Ким-Хун Ли L. Подводя итог, можно сказать, что идея этого алгоритма состоит в том, что мы перемешиваем первые n
чисел, чтобы сформировать начальный резервуар, а затем постепенно перезаписываем некоторые из них случайными числами, взятыми от остального ассортимента. Мы используем прогрессию geometri c, чтобы определить, сколько чисел пропускать на каждой итерации.
Предположим, что sampleSize - это число, а PopulationSize - это BigInt в следующем коде:
// Creates an array initialized with [0n, 1n, ... BigInt(count-1)].
function bigRange (count) {
const array = Array(count);
for (let i = 0; i < count; i += 1) {
array[i] = BigInt(i);
}
return array;
}
function bigSample (sampleSize, populationSize) {
const reservoir = shuffle(bigRange(sampleSize));
let record = BigInt(sampleSize);
let weight = exp(log(random()) / sampleSize);
while (true) {
const skipCount = floor(log(random()) / log(1 - weight));
record += BigInt(skipCount);
if (record >= populationSize) {
return reservoir;
}
reservoir[floor(random() * sampleSize)] = record;
record += BigInt(1);
weight *= exp(log(random()) / sampleSize);
}
}
Однако этот код не будет работать с произвольно большими числами, поскольку точность чисел с плавающей запятой ограничена. По мере роста PopulationSize в конечном итоге используются незначащие биты чисел с плавающей запятой, и у нас больше нет равномерного распределения, тогда мы попадаем на небезопасную целочисленную территорию, и, наконец, возможно, что skipCount
станет Infinity
. Зная это, я остаюсь в недоумении ...
- В какой момент эта функция становится ненадежной?
- Есть ли способ для меня для улучшения этого алгоритма для компенсации ограничений JavaScript?
- Есть ли лучшие альтернативы?
Обратите внимание, что мои знания о вероятности ограничен, и я написал этот код после недели, потраченной на копание в научных c статьях, которые я почти не понимал. У меня есть альтернативный подход, но он будет намного менее эффективным и потребует смешивания генерации случайных чисел с логикой генерации головоломки c.