Как работает алгоритм генерации уникальных чисел с генератором и map в Go
Что проверяют
Знаете ли вы идиомы Go: множество на мапе, генератор на канале или итераторе, и видите ли, где в простой задаче прячутся зависания и утечки. Следом часто спрашивают, что будет, если запросить больше чисел, чем есть в диапазоне, или вызвать код из нескольких горутин.
Как отвечать
Базовый алгоритм: генерируем случайное число, проверяем по множеству, новое добавляем и в множество, и в результирующий срез, повторяем до нужного количества.
- множество:
map[int]struct{}, пустая структура в значении не занимает памяти. Ёмкость лучше задать сразу,make(map[int]struct{}, count), чтобы мапа не перестраивалась по ходу; - порядок: итерация по мапе в Go намеренно случайна, поэтому порядок генерации хранят в срезе, а не восстанавливают из мапы;
- генератор: классический вариант это горутина, которая пишет в канал, современный это итератор
iter.Seqиrangeпо функции из Go 1.23. Итератору не нужна горутина: приbreakу потребителяyieldвернётfalse, и генерация остановится; - случайность: с Go 1.20 глобальный генератор засевается сам, а в
math/rand/v2функцииSeedна уровне пакета нет вовсе. Строкаrand.Seed(time.Now().UnixNano())из старых примеров не нужна.
Обязательно назовите сложность. Пока занята малая доля диапазона, повторы редки, но по мере заполнения попыток становится всё больше: чтобы собрать все n значений, нужно порядка n·ln n генераций. Если k сопоставимо с размером диапазона n, лучше rand.Perm(n) или частичное перемешивание Фишера-Йетса: ровно k шагов и никаких повторов.
Где спотыкаются
Не проверяют, что количество не больше размера диапазона. Запрос 150 уникальных чисел из rand.IntN(100) превращается в вечный цикл.
Делают генератор на канале без context или done-канала. Потребитель взял сколько нужно и ушёл, а горутина навсегда висит на записи в канал.
Пишут в одну мапу из нескольких горутин. Это не паника, которую можно перехватить, а фатальная ошибка рантайма concurrent map writes. Нужен sync.Mutex или одна горутина, владеющая мапой. То же с *rand.Rand из rand.New: он небезопасен для конкурентного использования.