Как правильно посеять генератор случайных чисел


160

Я пытаюсь сгенерировать случайную строку в Go, и вот код, который я написал до сих пор:

package main

import (
    "bytes"
    "fmt"
    "math/rand"
    "time"
)

func main() {
    fmt.Println(randomString(10))
}

func randomString(l int) string {
    var result bytes.Buffer
    var temp string
    for i := 0; i < l; {
        if string(randInt(65, 90)) != temp {
            temp = string(randInt(65, 90))
            result.WriteString(temp)
            i++
        }
    }
    return result.String()
}

func randInt(min int, max int) int {
    rand.Seed(time.Now().UTC().UnixNano())
    return min + rand.Intn(max-min)
}

Моя реализация очень медленная. Заполнение с использованием timeвозвращает одно и то же случайное число в течение определенного времени, поэтому цикл повторяется снова и снова. Как я могу улучшить свой код?


2
«If string (randInt (65,90))! = Temp {» выглядит так, как будто вы пытаетесь добавить дополнительную безопасность, но, эй, все случайно получается одинаково. Делая это, вы можете снизить энтропию.
Ян Матейка

3
Как примечание, нет необходимости преобразовывать в UTC в "time.Now (). UTC (). UnixNano ()". Время Unix рассчитывается начиная с эпохи, которая в любом случае является UTC.
Grzegorz Luczywo

2
Вы должны установить семя один раз, только один раз, и никогда больше, чем один раз. хорошо, если ваше приложение работает в течение нескольких дней, вы можете установить его один раз в день.
Casperah

Вы должны посеять один раз. И я думаю, что "Z" может никогда не появиться, я полагаю? Поэтому я предпочитаю использовать начальный индекс включительно и конечный индекс исключительно.
Jaehyun Yeom

Ответы:


232

Каждый раз, когда вы устанавливаете одно и то же семя, вы получаете одну и ту же последовательность. Поэтому, конечно, если вы устанавливаете начальное время в быстрый цикл, вы, вероятно, будете вызывать его с одним и тем же начальным числом много раз.

В вашем случае, когда вы вызываете свою randIntфункцию до тех пор, пока не получите другое значение, вы ждете, пока изменится время (возвращаемое Nano).

Как и для всех псевдослучайных библиотек , вы должны установить начальное значение только один раз, например, при инициализации вашей программы, если только вам не нужно специально воспроизвести заданную последовательность (что обычно делается только для отладки и модульного тестирования).

После этого вы просто звоните, Intnчтобы получить следующее случайное число.

Переместите rand.Seed(time.Now().UTC().UnixNano())строку из функции randInt в начало основной, и все будет быстрее.

Обратите внимание, что я думаю, вы можете упростить сборку строк:

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    rand.Seed(time.Now().UTC().UnixNano())
    fmt.Println(randomString(10))
}

func randomString(l int) string {
    bytes := make([]byte, l)
    for i := 0; i < l; i++ {
        bytes[i] = byte(randInt(65, 90))
    }
    return string(bytes)
}

func randInt(min int, max int) int {
    return min + rand.Intn(max-min)
}

Спасибо, что объяснили это, я думал, что это нужно посеять каждый раз.
copperMan

13
Вы также можете добавить rand.Seed(...)в функцию init(). init()вызывается автоматически раньше main(). Обратите внимание, что вам не нужно звонить init()с main()!
Джабба

2
@ Джабба Верно. Я держал свой ответ как можно более простым и не слишком далеким от вопроса, но ваше наблюдение верно.
Денис Сегюре

7
Обратите внимание, что ни один из ответов, опубликованных до сих пор, не инициализирует зерно криптографически безопасным способом. В зависимости от вашего приложения это может вообще не иметь значения или привести к катастрофическому отказу.
Инго Блехшмидт

3
@IngoBlechschmidt в math/randлюбом случае не является криптографически защищенным. Если это требование, crypto/randследует использовать.
Дункан Джонс

39

Я не понимаю, почему люди сеют с ценностью времени. По моему опыту, это никогда не было хорошей идеей. Например, хотя системные часы могут быть представлены в наносекундах, точность тактовой частоты системы не равна наносекундам.

Эту программу не следует запускать на игровой площадке Go, но если вы запустите ее на своем компьютере, вы получите приблизительную оценку того, какую точность вы можете ожидать. Я вижу приращения около 1000000 нс, поэтому приращение составляет 1 мс. Это 20 битов энтропии, которые не используются. Все время старшие биты в основном постоянны.

Степень, в которой это важно для вас, будет разной, но вы можете избежать ловушек значений начальных значений на основе тактовой частоты, просто используя в crypto/rand.Readкачестве источника начальное значение. Это даст вам то недетерминированное качество, которое вы, вероятно, ищете в своих случайных числах (даже если сама фактическая реализация ограничена набором отдельных и детерминированных случайных последовательностей).

import (
    crypto_rand "crypto/rand"
    "encoding/binary"
    math_rand "math/rand"
)

func init() {
    var b [8]byte
    _, err := crypto_rand.Read(b[:])
    if err != nil {
        panic("cannot seed math/rand package with cryptographically secure random number generator")
    }
    math_rand.Seed(int64(binary.LittleEndian.Uint64(b[:])))
}

Как примечание стороны, но по отношению к вашему вопросу. Вы можете создать свой собственный, rand.Sourceиспользуя этот метод, чтобы избежать затрат на блокировку источника. Функции randутилиты пакета удобны, но они также используют блокировки под капотом для предотвращения одновременного использования источника. Если вам это не нужно, вы можете избежать этого, создав собственный Sourceи использовать его не одновременно. В любом случае, вы НЕ должны перезаряжать генератор случайных чисел между итерациями, он никогда не был разработан для такого использования.


5
Этот ответ очень недооценен. Это необходимо сделать специально для инструментов командной строки, которые могут запускаться несколько раз в секунду. Спасибо
saeedgnu

1
При необходимости вы можете смешать PID и имя хоста / MAC, но помните, что заполнение RNG криптографически безопасным источником не делает его криптографически безопасным, поскольку кто-то может восстановить внутреннее состояние PRNG.
Ник Т

PID на самом деле не случайны. MAC могут быть клонированы. Как бы вы смешали их таким образом, чтобы не вносить нежелательный перекос / смещение?
Джон Лейдгрен

16

просто чтобы выбросить это для потомков: иногда может быть предпочтительнее генерировать случайную строку, используя исходную строку набора символов. Это полезно, если строка должна быть введена человеком вручную; Исключение 0, O, 1 и l может помочь уменьшить ошибку пользователя.

var alpha = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789"

// generates a random string of fixed size
func srand(size int) string {
    buf := make([]byte, size)
    for i := 0; i < size; i++ {
        buf[i] = alpha[rand.Intn(len(alpha))]
    }
    return string(buf)
}

и я обычно устанавливаю семя внутри init()блока. Они задокументированы здесь: http://golang.org/doc/effective_go.html#init


9
Насколько я понимаю, нет необходимости иметь -1в rand.Intn(len(alpha)-1). Это потому, что rand.Intn(n)всегда возвращает число, которое меньше n(другими словами: от нуля до n-1включительно).
оснастка

2
@snap правильно; фактически, включение -1in len(alpha)-1гарантировало бы, что число 9 никогда не использовалось в последовательности.
карбокатион

2
Следует также отметить, что исключение 0 (нуля) является хорошей идеей, поскольку вы приводите срез байта к строке, и это приводит к тому, что 0 становится нулевым байтом. Например, попробуйте создать файл с байтом «0» в середине и посмотрите, что произойдет.
Эрик Лагергрен

14

ОК, почему так сложно!

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    rand.Seed( time.Now().UnixNano())
    var bytes int

    for i:= 0 ; i < 10 ; i++{ 
        bytes = rand.Intn(6)+1
        fmt.Println(bytes)
        }
    //fmt.Println(time.Now().UnixNano())
}

Это основано на коде дистроя, но подходит для моих нужд.

Это умирает шесть (рандов целых 1 =< i =< 6)

func randomInt (min int , max int  ) int {
    var bytes int
    bytes = min + rand.Intn(max)
    return int(bytes)
}

Функция выше - это то же самое.

Я надеюсь, что эта информация была полезна.


Это будет все время возвращать одну и ту же последовательность, в том же порядке, если она вызывается несколько раз, что для меня не выглядит случайным. Посмотрите живой пример: play.golang.org/p/fHHENtaPv5 3 5 2 5 4 2 5 6 3 1
Томас Моденеис,

8
@ThomasModeneis: Это потому, что они подделывают время на детской площадке.
ofavre

1
Спасибо @ofavre, что фальшивое время действительно сначала бросило меня.
Джесси Чисхолм

1
Вам все еще нужно посеять перед вызовом rand.Intn(), в противном случае вы всегда будете получать один и тот же номер при каждом запуске вашей программы.
Флавио

Есть причина var bytes int? Какая разница для изменения выше , bytes = rand.Intn(6)+1чтобы bytes := rand.Intn(6)+1? Они оба, кажется, работают для меня, является ли один из них неоптимальным по какой-то причине?
pzkpfw

0

Это нано секунды, каковы шансы получить одно и то же семя дважды.
В любом случае, спасибо за помощь, вот мое конечное решение, основанное на всех входах.

package main

import (
    "math/rand"
    "time"
)

func init() {
    rand.Seed(time.Now().UTC().UnixNano())
}

// generates a random string
func srand(min, max int, readable bool) string {

    var length int
    var char string

    if min < max {
        length = min + rand.Intn(max-min)
    } else {
        length = min
    }

    if readable == false {
        char = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
    } else {
        char = "ABCDEFHJLMNQRTUVWXYZabcefghijkmnopqrtuvwxyz23479"
    }

    buf := make([]byte, length)
    for i := 0; i < length; i++ {
        buf[i] = char[rand.Intn(len(char)-1)]
    }
    return string(buf)
}

// For testing only
func main() {
    println(srand(5, 5, true))
    println(srand(5, 5, true))
    println(srand(5, 5, true))
    println(srand(5, 5, false))
    println(srand(5, 7, true))
    println(srand(5, 10, false))
    println(srand(5, 50, true))
    println(srand(5, 10, false))
    println(srand(5, 50, true))
    println(srand(5, 10, false))
    println(srand(5, 50, true))
    println(srand(5, 10, false))
    println(srand(5, 50, true))
    println(srand(5, 4, true))
    println(srand(5, 400, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
}

1
Re: what are the chances of getting the exact the exact same [nanosecond] twice?Отлично. Все зависит от внутренней точности реализации времени выполнения golang. Даже если единицы измерения - это наносекунды, наименьшее приращение может составлять миллисекунды или даже секунды.
Джесси Чисхолм

0

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

Самый важный шаг - вызвать функцию seed только один раз перед тем, как она будет запущена rand.Init(x). Seed использует предоставленное начальное значение для инициализации источника по умолчанию в детерминированном состоянии. Таким образом, было бы предложено вызвать его один раз перед фактическим вызовом функции для генератора псевдослучайных чисел.

Вот пример кода, создающего строку случайных чисел

package main 
import (
    "fmt"
    "math/rand"
    "time"
)



func main(){
    rand.Seed(time.Now().UnixNano())

    var s string
    for i:=0;i<10;i++{
    s+=fmt.Sprintf("%d ",rand.Intn(7))
    }
    fmt.Printf(s)
}

Причина, по которой я использовал Sprintf, заключается в том, что он позволяет простое форматирование строк.

Кроме того, In rand.Intn(7) Intn возвращает в качестве целого неотрицательное псевдослучайное число в [0,7).


0

@ [Денис Сегюре] опубликовал сообщение правильно. Но в моем случае мне нужно новое семя каждый раз, поэтому ниже код;

Incase вам нужны быстрые функции. Я использую как это.


func RandInt(min, max int) int {
    r := rand.New(rand.NewSource(time.Now().UnixNano()))
    return r.Intn(max-min) + min
}

func RandFloat(min, max float64) float64 {
    r := rand.New(rand.NewSource(time.Now().UnixNano()))
    return min + r.Float64()*(max-min)
}

источник


-2

Небольшое обновление из-за изменения api golang, пожалуйста, опустите .UTC ():

time.Now (). UTC () .UnixNano () -> time.Now (). UnixNano ()

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    rand.Seed(time.Now().UnixNano())
    fmt.Println(randomInt(100, 1000))
}

func randInt(min int, max int) int {
    return min + rand.Intn(max-min)
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.