Cómo sembrar correctamente el generador de números aleatorios

160

Estoy tratando de generar una cadena aleatoria en Go y aquí está el código que he escrito hasta ahora:

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

Mi implementación es muy lenta. Sembrar usando timetrae el mismo número aleatorio durante cierto tiempo, por lo que el ciclo se repite una y otra vez. ¿Cómo puedo mejorar mi código?

copperMan
fuente
2
Parece que "if string (randInt (65,90))! = Temp {" está tratando de agregar seguridad adicional, pero bueno, las cosas se ponen iguales una tras otra por casualidad. Al hacer esto, puede estar disminuyendo la entropía.
Jan Matějka
3
Como nota al margen, no hay necesidad de convertir a UTC en "time.Now (). UTC (). UnixNano ()". El tiempo de Unix se calcula desde Epoch, que es UTC de todos modos.
Grzegorz Luczywo
2
Debe establecer la semilla una vez, solo una vez, y nunca más de una vez. bueno, en caso de que su aplicación se ejecute durante días, puede configurarla una vez al día.
Casperah
Deberías sembrar una vez. Y creo que "Z" puede que nunca aparezca, supongo. Por lo tanto, prefiero usar el índice inicial inclusivo y el índice final exclusivo.
Jaehyun Yeom

Respuestas:

232

Cada vez que establece la misma semilla, obtiene la misma secuencia. Entonces, por supuesto, si está configurando la semilla en el tiempo en un ciclo rápido, probablemente la llame con la misma semilla muchas veces.

En su caso, mientras llama a su randIntfunción hasta que tenga un valor diferente, está esperando que cambie el tiempo (tal como lo devolvió Nano).

Como para todas las bibliotecas pseudoaleatorias , debe establecer la semilla solo una vez, por ejemplo, al inicializar su programa, a menos que necesite reproducir específicamente una secuencia dada (que generalmente solo se realiza para la depuración y la prueba de la unidad).

Después de eso, simplemente llame Intnpara obtener el siguiente entero aleatorio.

Mueva la rand.Seed(time.Now().UTC().UnixNano())línea desde la función randInt hasta el inicio de main y todo será más rápido.

Tenga en cuenta también que creo que puede simplificar su construcción de cadenas:

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)
}
Denys Séguret
fuente
Gracias por explicar eso, pensé que esto debía ser sembrado cada vez.
copperMan
13
También puede agregar rand.Seed(...)a la función init(). init()se llama automáticamente antes main(). Tenga en cuenta que no necesita llamar init()desde main()!
Jabba
2
@Jabba Derecha. Estaba manteniendo mi respuesta lo más simple posible y no muy lejos de la pregunta, pero su observación es correcta.
Denys Séguret
77
Tenga en cuenta que ninguna de las respuestas publicadas hasta ahora inicializa la semilla de forma criptográfica segura. Dependiendo de su aplicación, esto podría no importar en absoluto o podría resultar en una falla catastrófica.
Ingo Blechschmidt
3
@IngoBlechschmidt math/randno es criptográficamente seguro de todos modos. Si eso es un requisito, crypto/randdebe usarse.
Duncan Jones
39

No entiendo por qué las personas están sembrando con un valor de tiempo. En mi experiencia, esto nunca ha sido una buena idea. Por ejemplo, mientras que el reloj del sistema puede estar representado en nanosegundos, la precisión del reloj del sistema no es nanosegundos.

Este programa no debe ejecutarse en el patio de juegos Go, pero si lo ejecuta en su máquina obtendrá una estimación aproximada de qué tipo de precisión puede esperar. Veo incrementos de aproximadamente 1000000 ns, por lo que incrementos de 1 ms. Son 20 bits de entropía que no se usan. Todo el tiempo los bits altos son en su mayoría constantes.

El grado que esto le importa variará, pero puede evitar las trampas de los valores de semilla basados ​​en el reloj simplemente usando la crypto/rand.Readfuente como fuente para su semilla. Le dará esa calidad no determinista que probablemente esté buscando en sus números aleatorios (incluso si la implementación real en sí misma se limita a un conjunto de secuencias aleatorias distintas y deterministas).

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

Como nota al margen pero en relación con su pregunta. Puede crear el suyo rand.Sourceusando este método para evitar el costo de tener bloqueos que protegen la fuente. Las randfunciones de la utilidad del paquete son convenientes, pero también usan bloqueos debajo del capó para evitar que la fuente se use simultáneamente. Si no lo necesita, puede evitarlo creando el suyo propio Sourcey utilizándolo de manera no concurrente. De todos modos, NO debe volver a sembrar su generador de números aleatorios entre iteraciones, nunca fue diseñado para usarse de esa manera.

John Leidegren
fuente
55
Esta respuesta es muy poco apreciada. Especialmente para herramientas de línea de comandos que pueden ejecutarse varias veces en un segundo, esto es algo que debe hacer. Gracias
saeedgnu
1
Puede mezclar el PID y el nombre de host / MAC si es necesario, pero tenga en cuenta que sembrar el RNG con una fuente criptográficamente segura no lo hace criptográficamente seguro ya que alguien puede reconstruir el estado interno de PRNG.
Nick T
Los PID no son realmente aleatorios. Los MAC pueden ser clonados. ¿Cómo los mezclaría de una manera que no introduzca un sesgo / sesgo no deseado?
John Leidegren
16

solo para tirarlo a la posteridad: a veces puede ser preferible generar una cadena aleatoria usando una cadena de juego de caracteres inicial. Esto es útil si se supone que la cadena debe ser ingresada manualmente por un humano; excluir 0, O, 1 yl puede ayudar a reducir el error del usuario.

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

y típicamente pongo la semilla dentro de un init()bloque. Están documentados aquí: http://golang.org/doc/effective_go.html#init

jorelli
fuente
9
Por lo que he entendido bien, no hay necesidad de tener -1en rand.Intn(len(alpha)-1). Esto se debe a que rand.Intn(n)siempre devuelve un número menor que n(en otras palabras: de cero a n-1inclusivo).
Snap
2
@snap es correcto; de hecho, incluir el -1en len(alpha)-1habría garantizado que el número 9 nunca se usó en la secuencia.
carbocation
2
También debe tenerse en cuenta que excluir 0 (cero) es una buena idea porque está convirtiendo el segmento de bytes en una cadena, y eso hace que el 0 se convierta en un byte nulo. Por ejemplo, intente crear un archivo con un byte '0' en el medio y vea qué sucede.
Eric Lagergren
14

OK por qué tan complejo!

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

Esto se basa en el código de la distribución pero se ajusta a mis necesidades.

Son los seis (rands ints 1 =< i =< 6)

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

La función anterior es exactamente la misma cosa.

Espero que esta información haya sido de utilidad.

Luviz
fuente
Eso devolverá todo el tiempo la misma secuencia, en el mismo orden si se llama varias veces, que no me parece muy aleatorio. Ver ejemplo en vivo: play.golang.org/p/fHHENtaPv5 3 5 2 5 4 2 5 6 3 1
Thomas Modeneis
8
@ThomasModeneis: Eso es porque fingen tiempo en el patio de recreo.
ofavre
1
Gracias @ofavre, ese tiempo falso realmente me sorprendió al principio.
Jesse Chisholm
1
Todavía necesita sembrar antes de llamar rand.Intn(), de lo contrario siempre obtendrá el mismo número cada vez que ejecute su programa.
Flavio Copes
¿Alguna razón para var bytes int? ¿Cuál es la diferencia para cambiar lo anterior bytes = rand.Intn(6)+1a bytes := rand.Intn(6)+1? Ambos parecen funcionar para mí, ¿es uno de ellos subóptimo por alguna razón?
pzkpfw
0

Son nano segundos, ¿cuáles son las posibilidades de obtener la misma semilla dos veces?
De todos modos, gracias por la ayuda, aquí está mi solución final basada en todos los aportes.

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))
}
RoboTamer
fuente
1
re: what are the chances of getting the exact the exact same [nanosecond] twice?excelente. Todo depende de la precisión interna de la implementación de los tiempos de ejecución de golang. Aunque las unidades son nano-segundos, el incremento más pequeño podría ser un milisegundo o incluso un segundo.
Jesse Chisholm
0

Si su objetivo es solo generar una picadura de número aleatorio, entonces creo que no es necesario complicarlo con llamadas a múltiples funciones o restablecer la semilla cada vez.

El paso más importante es llamar a la función semilla solo una vez antes de ejecutarse realmente rand.Init(x). La semilla utiliza el valor de semilla proporcionado para inicializar la fuente predeterminada a un estado determinista. Por lo tanto, se sugiere llamarlo una vez antes de la llamada a la función real al generador de números pseudoaleatorios.

Aquí hay un código de muestra que crea una cadena de números aleatorios

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

La razón por la que usé Sprintf es porque permite un formato de cadena simple.

Además, In rand.Intn(7) Intn devuelve, como int, un número pseudoaleatorio no negativo en [0,7].

Capitán Levi
fuente
0

@ [Denys Séguret] ha publicado correctamente. Pero en mi caso, necesito nuevas semillas cada vez, por lo tanto, debajo del código;

En caso de que necesite funciones rápidas. Yo uso así.


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

fuente

ACERO
fuente
-2

Pequeña actualización debido al cambio de la API de Golang, omita .UTC ():

Ahora(). 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)
}
letanthang
fuente