belun.app Блог
EN

Как честно выбрать случайного победителя (и доказать это)

Почему «вытянуть из шапки» не работает, что делает розыгрыш имён по-настоящему честным и как проводить розыгрыши, с которыми никто не спорит.

Руки вытягивают выигрышный билет из стопки во время розыгрыша призов

Каждый уверен, что умеет выбирать наугад. Закрыл глаза, ткнул в список, готово. На деле люди ужасно с этим справляются: мы избегаем краёв списка, тянемся к знакомым именам, а когда перемешиваем бумажки в миске, последние брошенные удобно остаются сверху.

Классический поучительный случай — американская призывная лотерея 1969 года. Капсулы с датами рождения складывали в банку по месяцам, начиная с января. Перемешали плохо, декабрьские капсулы остались у верха и вытягивались раньше — и родившихся в декабре заметно чаще призывали в армию. Статистики разнесли методику за считаные недели. Ставки в вашем офисном розыгрыше пониже, но механизм провала тот же: жеребьёвка, которая выглядит случайной, но ею не является.

Что значит «честно»

Честный розыгрыш даёт каждой записи ровно одинаковую вероятность. Не примерно одинаковую. Ровно.

Программы ошибаются здесь двумя популярными способами. Первый — слабый источник случайности: Math.random в JavaScript никогда не предназначался для задач, где важна честность, и в старых браузерах его вывод был настолько предсказуем, что исследователи восстанавливали будущие значения по прошлым. Второй тоньше — смещение по модулю. Наивный код накладывает случайное число на список оператором %, и если длина списка не делит диапазон генератора нацело, первые записи выпадают чуть чаще. Перекос крошечный, доли процента, но он реален, измерим — а для розыгрыша призов попросту не нужен.

Случайный выбор имени обходит обе ловушки. Он использует crypto.getRandomValues — криптографический генератор браузера, тот самый, что создаёт ключи шифрования, — и отбраковку значений вместо голого модуля. При десяти именах у каждого шанс один к десяти. Не «примерно».

Как провести чистый розыгрыш

Вставьте список, по одному имени на строку. Несколько практических приёмов:

  • Билеты с весом. Каждая строка — отдельный билет. Заработал человек три билета — сделал репост, привёл друга, что там у вас по правилам — вставьте его имя три раза.
  • Несколько призов. Поставьте в поле победителей 3, и один розыгрыш вернёт три разных имени в порядке выпадения. Первое имя — первый приз.
  • Раунды подряд. Включите «Убирать победителей из списка» — выигравший исчезает из поля после розыгрыша. Никто не выигрывает дважды, а в классе никого не спросят второй раз, пока остальные отсиживаются.

Чего инструмент намеренно не делает — так это не хранит ничего. Список живёт во вкладке браузера и умирает вместе с ней. Для розыгрыша, где участники оставляли личные данные, это важно: вы не отправляете их имена на чужой сервер ради одного клика.

Этап, который все пропускают: доказательство

Честный метод, который нельзя показать, стоит половины честного метода. Чем бы вы ни пользовались:

Объявите механизм до розыгрыша, а не после. «Победителя выберет генератор случайных имён 30 июля» звучит лучше расплывчатого «победители будут определены».

Проводите розыгрыш на глазах у людей. Запишите экран во время клика или сделайте это в прямом эфире. Вся процедура занимает секунд десять, а десятисекундное видео закрывает любую ветку «всё куплено» до её появления.

Сохраните исходный список. Спросят, почему кто-то не выиграл, — покажете, что именно участвовало в розыгрыше.

Вот, собственно, и всё. Вставьте имена в Случайный выбор имени, нажмите кнопку при свидетелях — и пусть криптография браузера сделает то, чего тыканье пальцем в список не умело никогда.

Попробуйте инструмент

Случайный выбор имени →