Come seminare correttamente il generatore di numeri casuali


160

Sto cercando di generare una stringa casuale in Go ed ecco il codice che ho scritto finora:

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

La mia implementazione è molto lenta. Seeding usando timeporta lo stesso numero casuale per un certo tempo, quindi il ciclo scorre ripetutamente. Come posso migliorare il mio codice?


2
"If string (randInt (65,90))! = Temp {" sembra che tu stia provando ad aggiungere ulteriore sicurezza ma, ehi, le cose ottengono la stessa una dopo l'altra per caso. In questo modo potresti effettivamente abbassare l'entropia.
Jan Matějka,

3
Come nota a margine, non è necessario convertire in UTC in "time.Now (). UTC (). UnixNano ()". Il tempo di Unix viene calcolato dall'epoca che è comunque UTC.
Grzegorz Luczywo,

2
Dovresti impostare il seme una volta, una sola volta e mai più di una volta. bene, nel caso in cui l'applicazione venga eseguita per giorni, è possibile impostarla una volta al giorno.
Casperah,

Dovresti seminare una volta. E penso che "Z" potrebbe non apparire mai, immagino? Quindi preferisco usare l'inizio dell'indice incluso e fine l'indice esclusivo.
Jaehyun Yeom il

Risposte:


232

Ogni volta che imposti lo stesso seme, ottieni la stessa sequenza. Quindi, naturalmente, se stai impostando il seme sull'ora in un ciclo veloce, probabilmente lo chiamerai con lo stesso seme molte volte.

Nel tuo caso, mentre chiami la tua randIntfunzione finché non hai un valore diverso, stai aspettando che il tempo (come restituito da Nano) cambi.

Come per tutte le librerie pseudo-casuali , è necessario impostare il seme una sola volta, ad esempio durante l'inizializzazione del programma, a meno che non sia necessario riprodurre in modo specifico una determinata sequenza (che di solito viene eseguita solo per il debug e il test dell'unità).

Dopodiché devi semplicemente chiamare Intnper ottenere il prossimo numero intero casuale.

Sposta la rand.Seed(time.Now().UTC().UnixNano())linea dalla funzione randInt all'inizio della principale e tutto sarà più veloce.

Nota anche che penso che puoi semplificare la creazione di stringhe:

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

Grazie per averlo spiegato, ho pensato che fosse necessario seminare ogni volta.
CopperMan,

13
È inoltre possibile aggiungere rand.Seed(...)alla funzione init(). init()viene chiamato automaticamente prima main(). Si noti che non è necessario alla chiamata init()da main()!
Jabba,

2
@Jabba Giusto. Stavo mantenendo la mia risposta il più semplice possibile e non troppo lontana dalla domanda, ma la tua osservazione è giusta.
Denys Séguret,

7
Si noti che nessuna delle risposte pubblicate finora inizializza il seme in modo crittograficamente sicuro. A seconda dell'applicazione, ciò potrebbe non avere alcuna importanza o potrebbe causare un errore irreversibile.
Ingo Blechschmidt,

3
@IngoBlechschmidt math/randnon è comunque sicuro crittograficamente. Se questo è un requisito, crypto/randdovrebbe essere usato.
Duncan Jones,

39

Non capisco perché le persone stanno seminando con un valore temporale. Questa esperienza non è mai stata una buona idea. Ad esempio, mentre l'orologio di sistema è forse rappresentato in nanosecondi, la precisione dell'orologio di sistema non è di nanosecondi.

Questo programma non dovrebbe essere eseguito nel parco giochi Go ma se lo esegui sulla tua macchina otterrai una stima approssimativa su quale tipo di precisione puoi aspettarti. Vedo incrementi di circa 1000000 ns, quindi incrementi di 1 ms. Sono 20 bit di entropia che non vengono utilizzati. Nel frattempo i bit alti sono per lo più costanti.

Il grado che conta per te varierà, ma puoi evitare trappole dei valori dei semi basati sull'orologio semplicemente usando crypto/rand.Readcome fonte per il tuo seme. Ti darà quella qualità non deterministica che probabilmente stai cercando nei tuoi numeri casuali (anche se l'implementazione stessa è limitata a una serie di sequenze casuali distinte e deterministiche).

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[:])))
}

Come nota a margine ma in relazione alla tua domanda. Puoi crearne uno tuo rand.Sourceusando questo metodo per evitare il costo di avere blocchi che proteggono la fonte. Le randfunzioni di utilità del pacchetto sono convenienti ma usano anche i blocchi sotto il cofano per impedire che la sorgente venga utilizzata contemporaneamente. Se non ti serve, puoi evitarlo creandone uno tuo Sourcee utilizzandolo in modo non simultaneo. Indipendentemente da ciò, NON dovresti eseguire il reseeding del generatore di numeri casuali tra le iterazioni, non è mai stato progettato per essere utilizzato in questo modo.


5
Questa risposta è molto sottovalutata. Specialmente per gli strumenti da riga di comando che possono essere eseguiti più volte in un secondo, questo è un must. Grazie
saeedgnu

1
È possibile combinare PID e nome host / MAC, se necessario, ma attenzione che il seeding di RNG con una fonte crittograficamente sicura non lo rende crittograficamente sicuro in quanto qualcuno può ricostruire lo stato interno del PRNG.
Nick T

I PID non sono realmente casuali. I MAC possono essere clonati. Come li mescoleresti in un modo che non introduce uno sbilancio / inclinazione indesiderato?
John Leidegren,

16

solo per buttarlo fuori per i posteri: a volte può essere preferibile generare una stringa casuale usando una stringa iniziale di set di caratteri. Ciò è utile se la stringa deve essere inserita manualmente da un essere umano; esclusi 0, O, 1 e 1 possono aiutare a ridurre l'errore dell'utente.

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

e di solito imposto il seme all'interno di un init()blocco. Sono documentati qui: http://golang.org/doc/effective_go.html#init


9
Per quanto ho capito bene, non c'è bisogno di avere -1dentro rand.Intn(len(alpha)-1). Questo perché rand.Intn(n)restituisce sempre un numero inferiore a n(in altre parole: da zero a n-1inclusivo).
scatto il

2
@snap è corretto; infatti, includendo -1in, si len(alpha)-1sarebbe garantito che il numero 9 non fosse mai usato nella sequenza.
carbocation

2
Va anche notato che escludere 0 (zero) è una buona idea perché stai lanciando la porzione di byte in una stringa e questo fa sì che 0 diventi un byte null. Ad esempio, prova a creare un file con un byte '0' nel mezzo e guarda cosa succede.
Eric Lagergren,

14

OK perché così complesso!

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

Questo si basa sul codice della distrofia ma si adatta alle mie esigenze.

Sono morti sei (rands ints 1 =< i =< 6)

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

La funzione sopra è esattamente la stessa cosa.

Spero che queste informazioni siano state utili.


Ciò restituirà sempre la stessa sequenza, nello stesso ordine se chiamato più volte, per me non sembra molto casuale. Guarda l'esempio dal vivo: play.golang.org/p/fHHENtaPv5 3 5 2 5 4 2 5 6 3 1
Thomas Modeneis,

8
@ThomasModeneis: Questo perché fanno finta di stare nel parco giochi.
ofavre,

1
Grazie @ofavre, quel tempo falso mi ha davvero gettato all'inizio.
Jesse Chisholm,

1
Devi ancora eseguire il seeding prima di chiamare rand.Intn(), altrimenti otterrai sempre lo stesso numero ogni volta che esegui il programma.
Flavio Copes,

Qualche motivo per var bytes int? Qual è la differenza a cambiare quanto sopra bytes = rand.Intn(6)+1a bytes := rand.Intn(6)+1? Entrambi sembrano funzionare per me, uno dei due non è ottimale per qualche motivo?
pzkpfw,

0

Sono nano secondi, quali sono le possibilità di ottenere lo stesso seme due volte.
Comunque, grazie per l'aiuto, ecco la mia soluzione finale basata su tutti gli input.

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?eccellente. Tutto dipende dalla precisione interna dell'implementazione dei tempi di esecuzione del Golang. Anche se le unità sono nano-secondi, l'incremento più piccolo potrebbe essere un milli-secondo o anche un secondo.
Jesse Chisholm,

0

Se il tuo obiettivo è solo quello di generare una puntura di un numero casuale, penso che non sia necessario complicarlo con più chiamate di funzione o reimpostare il seme ogni volta.

Il passo più importante è chiamare la funzione seed solo una volta prima di eseguirla rand.Init(x). Seed utilizza il valore seed fornito per inizializzare l'origine predefinita in uno stato deterministico. Quindi, si suggerisce di chiamarlo una volta prima della chiamata di funzione effettiva al generatore di numeri pseudo-casuali.

Ecco un codice di esempio che crea una stringa di numeri casuali

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

Il motivo per cui ho usato Sprintf è perché consente una semplice formattazione delle stringhe.

Inoltre, In rand.Intn(7) Intn restituisce, come int, un numero pseudo-casuale non negativo in [0,7).


0

@ [Denys Séguret] ha pubblicato correttamente. Ma nel mio caso ho bisogno di nuovo seme ogni volta quindi sotto il codice;

Nel caso abbiate bisogno di funzioni rapide. Uso così.


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

fonte


-2

Piccolo aggiornamento dovuto alla modifica dell'API Golang, per favore ometti .UTC ():

momento attuale(). 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)
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.