Как написать функцию в Go, которая возвращает слайс с заданным количеством уникальных случайных чисел без использования готовых библиотек
Что проверяют
Выберете ли вы подходящий алгоритм и проверите ли граничные условия, а не просто напишете цикл.
Как отвечать
Начните с уточнения: из какого диапазона числа и как соотносятся n и размер диапазона. От этого зависит алгоритм.
Способ 1, набор через множество. Генерируем и отбрасываем повторы:
func uniqueRandom(n, max int) ([]int, error) {
if n > max {
return nil, fmt.Errorf("нельзя выбрать %d уникальных из %d", n, max)
}
seen := make(map[int]struct{}, n)
out := make([]int, 0, n)
for len(out) < n {
v := rand.Intn(max)
if _, ok := seen[v]; ok {
continue
}
seen[v] = struct{}{}
out = append(out, v)
}
return out, nil
}Хорош, когда n заметно меньше диапазона. Слабое место: при n, близком к max, последние числа ищутся очень долго, потому что почти каждая попытка попадает в занятое.
Способ 2, частичное перемешивание Фишера-Йетса. Гарантированное время без повторных попыток:
func uniqueRandom(n, max int) []int {
pool := make([]int, max)
for i := range pool {
pool[i] = i
}
for i := 0; i < n; i++ {
j := i + rand.Intn(max-i)
pool[i], pool[j] = pool[j], pool[i]
}
return pool[:n]
}Стоит памяти на весь диапазон, зато ровно n шагов и равномерное распределение.
Выбор: маленькая доля от большого диапазона это первый способ, большая доля или маленький диапазон это второй.
Что стоит упомянуть про генератор: math/rand подходит для обычных задач, для токенов и паролей нужен crypto/rand. С версии 1.20 глобальный источник инициализируется сам, и ручной вызов установки зерна больше не нужен.
Где спотыкаются
Не проверяют случай, когда запрошено больше уникальных значений, чем есть в диапазоне.
Пишут первый способ и не замечают деградации при большом n.
Ищут дубли линейным поиском по срезу и получают квадрат.
Берут math/rand там, где нужна криптографическая стойкость.