Как реализовать генерацию случайных чисел без повторений в JavaScript
Что проверяют
Знаете ли вы перемешивание Фишера-Йетса и умеете ли выбрать алгоритм по соотношению нужного количества чисел и размера диапазона. Следом обычно спрашивают: диапазон миллиард, нужно пять чисел, что делать? И почему нельзя перемешать массив сортировкой со случайным компаратором.
Как отвечать
Покажите, что способ зависит от k (сколько чисел нужно) и n (размер диапазона):
- k сравнимо с n: массив всех значений и Фишер-Йетс. Идёте с конца, для позиции
iберёте случайныйjот 0 доiвключительно черезMath.floor(Math.random() * (i + 1))и меняете элементы местами. Время и память O(n), каждая перестановка равновероятна. Если нужно только k чисел, достаточно k шагов иslice; - k намного меньше n:
Setи генерация до нужного размера. Пока k далеко от n, повторов почти нет. При k, близком к n, попыток становится много, это задача коллекционера купонов; - огромный диапазон: частичное перемешивание на
Map, где хранятся только затронутые индексы, либо алгоритм Флойда. Оба дают k чисел за k шагов и O(k) памяти, не создавая массив на миллиард элементов; - качество случайности:
Math.random()не криптостойкий. Для розыгрышей и всего, что связано с деньгами, беритеcrypto.getRandomValues(), а смещение от взятия остатка по модулю убирайте отбрасыванием неподходящих значений.
Проговорите граничные случаи: k больше размера диапазона даёт ошибку, а не вечный цикл; min больше max; обе границы включительно или нет.
Где спотыкаются
Перемешивают через sort(() => Math.random() > 0.5 ? 1 : -1). Компаратор противоречит сам себе, результат зависит от алгоритма сортировки движка, и распределение заметно неравномерное.
Используют Math.round вместо Math.floor. Крайние значения диапазона выпадают вдвое реже остальных, а при неудачной формуле появляется число за границей.
Крутят while с проверкой includes по массиву. Это O(k²), а если попросить больше чисел, чем есть в диапазоне, цикл не завершится никогда.