Как выбирают числа, пункты и порядок списка
Генератор обычно отдаёт большое целое число или дробь, а человеку нужен маленький понятный результат. На этом последнем переходе и появляются разные виды рандомизаторов.
Число из диапазона. Если генератор выдал равномерную дробь u от 0 до 1, целое число от a до b включительно можно получить так:
Результат = floor(u × (b − a + 1)) + a
Функция floor отбрасывает дробную часть. При a = 1 и b = 6 отрезок от 0 до 1 делится на шесть равных частей, как виртуальный кубик. По этой схеме работает выбор случайных чисел из диапазона: можно указать границы, количество результатов и исключения. Текущая числовая версия использует Math.random(), поэтому подходит для обычного выбора, но не для секретных ключей.
Один или несколько пунктов. Список из n строк нумеруется от 0 до n − 1, затем генератор выбирает индекс. Если повторы разрешены, каждый выбор выполняется заново. Если они запрещены, удобно перемешать копию списка и взять первые k строк. Так устроен случайный выбор из списка.
Новый порядок всего списка. Здесь используют алгоритм Фишера-Йетса. Он идёт с последнего элемента к первому, на каждом шаге выбирает любую позицию от начала до текущей и меняет элементы местами. При равномерном выборе индекса каждая перестановка получает одинаковый шанс. Этот алгоритм можно увидеть в работе, если перемешать строки списка.
Популярный короткий трюк array.sort(() => Math.random() - 0.5) хуже. Сортировка ждёт согласованное правило сравнения, а случайный ответ нарушает его. Количество и порядок сравнений зависят от реализации, поэтому одни перестановки могут появляться чаще других. Фишер-Йетс длиннее на несколько строк, зато его поведение понятно.
Распределение по командам. Сначала весь список перемешивают, затем последовательно раздают элементы по группам нужного размера. Если число участников не делится поровну, первые несколько групп получают по одному дополнительному человеку. На этом принципе работает случайное разделение списка на группы.
Есть ещё одна маленькая ловушка. Браузер может выдать любое 32-битное число от 0 до 4 294 967 295. Если взять остаток от деления на 10, диапазон из 232 значений делится на десять с остатком 6. Первые шесть вариантов получат по одному лишнему исходному значению.
Разница крошечная, но исправляется просто: отбросить верхний неполный хвост и запросить число заново. Списковые инструменты eCalc получают значения через crypto.getRandomValues() и используют именно такое отбрасывание перед делением. Хорошая случайность здесь состоит не из одного магического вызова, а из нескольких аккуратных решений подряд.