Як правильно насіти генератор випадкових чисел


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 {" виглядає так, що ви намагаєтеся додати додаткову безпеку, але ей, речі стають однаковими одна за одною випадково. Роблячи це, ви можете фактично знизити ентропію.
yaccz

3
Як зауваження, немає необхідності перетворювати на UTC у "time.Now (). UTC (). UnixNano ()". Час Unix обчислюється з епохи, яка так чи інакше є UTC.
Grzegorz Luczywo

2
Ви повинні встановити насіння один раз, лише один раз, і ніколи більше одного разу. добре, якщо ваша програма працює протягом днів, ви можете встановити її один раз на день.
Каспера

Ви повинні сіяти один раз. І я думаю, що "Z" може ніколи не з'явитися, я думаю? Тому я вважаю за краще використовувати індекс включно та закінчувати індекс виключно.
Jaehyun Yeom

Відповіді:


232

Кожен раз, коли ви встановлюєте одне і те ж насіння, ви отримуєте однакову послідовність. Звичайно, якщо ви встановлюєте час насіння в швидкий цикл, ви, ймовірно, будете називати його тим же насінням багато разів.

У вашому випадку, коли ви викликаєте свою randIntфункцію до тих пір, поки ви не отримаєте інше значення, ви чекаєте, коли час (як повернуто Нано) зміниться.

Що стосується всіх псевдовипадкових бібліотек , ви повинні встановити насіння лише один раз, наприклад, при ініціалізації вашої програми, якщо вам спеціально не потрібно відтворити задану послідовність (що зазвичай робиться лише для налагодження та тестування одиниць).

Після цього ви просто зателефонуєте, 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)
}

Дякую за пояснення, що я думав, що це потрібно висаджувати кожен раз.
мідьМан

13
Ви також можете додати rand.Seed(...)до функції init(). init()викликається автоматично раніше main(). Зверніть увагу , що вам не потрібно , щоб виклик init()від main()!
Джабба

2
@Jabba Правильно. Я тримав свою відповідь якомога простіше і не надто далеко від питання, але ваше зауваження вірно.
Denys Séguret

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())
}

Це засновано на коді diststroy, але підходить для моїх потреб.

Це помер шість (rands ints 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
Thomas Modeneis

8
@ThomasModeneis: Це тому, що вони підробляють час на ігровому майданчику.
ofavre

1
Дякую @ofavre, що фальшивий час насправді спочатку кинув мене.
Джессі Чісгольм

1
Перед тим, як зателефонувати rand.Intn(), вам все-таки потрібно посіяти , інакше ви завжди отримаєте однакову кількість кожного разу, коли запускаєте програму.
Flavio Copes

Якась причина 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?Відмінно. Все залежить від внутрішньої точності виконання голангових часів. Незважаючи на те, що одиниці є наносекундами, найменший приріст може становити мільйон або навіть секунду.
Jesse Chisholm

0

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

Найважливіший крок - викликати функцію насіння лише один раз, перш ніж насправді запустити rand.Init(x). Насіння використовує надане значення насіння для ініціалізації джерела за замовчуванням до детермінованого стану. Отже, було б запропоновано викликати його один раз перед фактичним викликом функції генератору псевдовипадкових чисел.

Ось зразок коду, який створює рядок випадкових чисел

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 повертає як int невід'ємне псевдовипадкове число у [0,7).


0

@ [Denys Séguret] опублікував правильно. Але в моєму випадку мені потрібно щойно нове насіння, звідси нижче коду;

Якщо вам потрібні швидкі функції. Я використовую так.


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, будь ласка, опустіть .UTC ():

час.Зараз (). 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.