Como propagar corretamente o gerador de números aleatórios


160

Estou tentando gerar uma seqüência aleatória no Go e aqui está o código que escrevi até agora:

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

Minha implementação é muito lenta. A propagação usando timetraz o mesmo número aleatório por um certo tempo, portanto o loop itera repetidamente. Como posso melhorar meu código?


2
O "if string (randInt (65,90))! = Temp {" parece que você está tentando adicionar segurança extra, mas ei, as coisas ficam iguais uma após a outra por acaso. Ao fazer isso, você pode estar realmente diminuindo a entropia.
— yaccz

2
Como uma observação lateral, não há necessidade de converter para UTC em "time.Now (). UTC (). UnixNano ()". O tempo Unix é calculado desde a época, que é UTC de qualquer maneira.
— Grzegorz Luczywo

2
Você deve definir a semente uma vez, apenas uma vez e nunca mais de uma vez. bem, caso seu aplicativo seja executado por dias, você poderá defini-lo uma vez por dia.
— Casperah

Você deve semear uma vez. E eu acho que "Z" pode nunca aparecer, eu acho? Portanto, prefiro usar o índice inicial e o índice final exclusivo.
— Jaehyun Yeom

Respostas:


232

Cada vez que você define a mesma semente, obtém a mesma sequência. Então, é claro, se você estiver definindo a semente no tempo em um ciclo rápido, provavelmente a chamará com a mesma semente muitas vezes.

No seu caso, como você está chamando seu randInt função até ter um valor diferente, você está aguardando a hora (conforme retornada pelo Nano) mudar.

Quanto a todas as bibliotecas pseudo-aleatórias , você deve definir a semente apenas uma vez, por exemplo, ao inicializar seu programa, a menos que precise especificamente reproduzir uma determinada sequência (o que geralmente é feito apenas para depuração e teste de unidade).

Depois disso, basta ligar Intnpara obter o próximo número inteiro aleatório.

Mova a rand.Seed(time.Now().UTC().UnixNano())linha da função randInt para o início da principal e tudo será mais rápido.

Observe também que acho que você pode simplificar sua construção de strings:

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

Obrigado por explicar isso, eu pensei que isso precisa ser semeado sempre.
— usar o seguinte comando

13
Você também pode adicionar rand.Seed(...)à função init().init()é chamado automaticamente antes main(). Observe que você não precisa ligar init()demain() !
— Jabba

2
@Jabba Certo. Eu estava mantendo minha resposta o mais simples possível e não muito longe da pergunta, mas sua observação está certa.
— Denys Séguret

7
Observe que nenhuma das respostas postadas até o momento inicializa a semente de maneira criptograficamente segura. Dependendo da sua aplicação, isso pode não ter importância ou resultar em falha catastrófica.
— Ingo Blechschmidt

3
@IngoBlechschmidt math/randnão é criptograficamente seguro de qualquer maneira. Se isso é um requisito, crypto/randdeve ser usado.
— Duncan Jones

39

Não entendo por que as pessoas estão semeando com um valor de tempo. Na minha experiência, isso nunca foi uma boa ideia. Por exemplo, embora o relógio do sistema seja talvez representado em nanossegundos, a precisão do relógio do sistema não é de nanossegundos.

Este programa não deve ser executado no playground Go, mas se você o executar na máquina, obterá uma estimativa aproximada do tipo de precisão que você pode esperar. Eu vejo incrementos de cerca de 1000000 ns, então incrementos de 1 ms. São 20 bits de entropia que não são usados. Enquanto isso, os bits altos são na maioria constantes.

O grau em que isso é importante para você varia, mas você pode evitar as armadilhas dos valores das sementes baseadas no relógio simplesmente usando a crypto/rand.Readfonte como para sua semente. Isso fornecerá a qualidade não determinística que você provavelmente está procurando em seus números aleatórios (mesmo que a própria implementação seja limitada a um conjunto de seqüências aleatórias distintas e determinísticas).

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 uma nota lateral, mas em relação à sua pergunta. Você pode criar o seu próprio rand.Sourceusando esse método para evitar o custo de ter bloqueios protegendo a origem. orand funções do utilitário de pacotes são convenientes, mas também usam travas sob o capô para impedir que a fonte seja usada simultaneamente. Se você não precisar, pode evitá-lo criando o seu Sourcee usá-lo de maneira não simultânea. Independentemente disso, você NÃO deve realizar uma nova propagação do gerador de números aleatórios entre as iterações, nunca foi projetado para ser usado dessa maneira.


5
Esta resposta é muito subestimada. Especialmente para ferramentas de linha de comando que podem ser executadas várias vezes em um segundo, isso é obrigatório. Obrigado
— saeedgnu

1
Você pode misturar o PID e o nome do host / MAC, se necessário, mas lembre-se de que semear o RNG com uma fonte criptograficamente segura não o torna criptograficamente seguro, pois alguém pode reconstruir o estado interno do PRNG.
— Nick T

Os PIDs não são realmente aleatórios. Os MACs podem ser clonados. Como você os misturaria de uma maneira que não introduza uma inclinação / tendência indesejadas?
— John Leidegren

16

apenas para descartá-lo para a posteridade: às vezes pode ser preferível gerar uma sequência aleatória usando uma sequência inicial de caracteres. Isso é útil se a string for inserida manualmente por um ser humano; excluindo 0, O, 1 e eu podemos ajudar a reduzir o erro do usuário.

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 normalmente coloco a semente dentro de um init()bloco. Eles estão documentados aqui: http://golang.org/doc/effective_go.html#init


9
Tanto quanto eu entendi, não há necessidade de ter -1em rand.Intn(len(alpha)-1). Isso ocorre porque rand.Intn(n)sempre retorna um número menor que n(em outras palavras: de zero a n-1inclusivo).
— snap

2
@snap está correto; de fato, incluir o -1in len(alpha)-1garantiria que o número 9 nunca fosse usado na sequência.
— carbocation

2
Também deve ser observado que excluir 0 (zero) é uma boa ideia, porque você está convertendo a fatia de bytes em uma sequência e faz com que o 0 se torne um byte nulo. Por exemplo, tente criar um arquivo com um byte '0' no meio e veja o que acontece.
— Eric Lagergren

14

OK por que tão complexo!

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

Isso é baseado no código do distro, mas adequado às minhas necessidades.

São seis (rands ints 1 =< i =< 6)

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

A função acima é exatamente a mesma coisa.

Espero que esta informação tenha sido útil.


Isso retornará o tempo todo a mesma sequência, na mesma ordem, se chamada várias vezes, que não me parece muito aleatória. Veja exemplo ao vivo: play.golang.org/p/fHHENtaPv5 3 5 2 5 4 2 5 6 3 1
— Thomas Modeneis

8
@ThomasModeneis: Isso é porque eles fingem tempo no parquinho.
— ofavre 21/11

1
Obrigado @ofavre, esse tempo falso realmente me jogou no começo.
— Jesse Chisholm

1
Você ainda precisa se propagar antes de ligar rand.Intn(), caso contrário, sempre obterá o mesmo número sempre que executar o programa.
— Flavio Copes

Alguma razão para var bytes int? Qual é a diferença de alterar o acima bytes = rand.Intn(6)+1para bytes := rand.Intn(6)+1? Ambos parecem funcionar para mim, é um deles sub-ideal por algum motivo?
— Pzkpfw 30/07/19

0

São nano segundos, quais são as chances de obter a mesma semente duas vezes.
De qualquer forma, obrigado pela ajuda, aqui está minha solução final com base em todas as informações.

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?Excelente. Tudo depende da precisão interna da implementação dos tempos de execução do golang. Mesmo que as unidades sejam nanossegundos, o menor incremento pode ser um milésimo de segundo ou até um segundo.
— Jesse Chisholm

0

Se o seu objetivo é apenas gerar um número aleatório, acho desnecessário complicá-lo com várias chamadas de função ou redefinir a semente toda vez.

O passo mais importante é chamar a função de semente apenas uma vez antes de realmente executar rand.Init(x). Semente usa o valor de semente fornecido para inicializar a Origem padrão para um estado determinístico. Portanto, seria sugerido chamá-lo uma vez antes da chamada de função real para o gerador de números pseudo-aleatórios.

Aqui está um código de exemplo criando uma sequência de números aleatórios

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

A razão pela qual eu usei o Sprintf é porque ele permite formatação simples de string.

Além disso, In rand.Intn(7) Intn retorna, como int, um número pseudo-aleatório não negativo em [0,7).


0

@ [Denys Séguret] postou correto. Mas, no meu caso, preciso de novas sementes toda vez que estão abaixo do código;

Caso você precise de funções rápidas. Eu uso assim.


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

Pequena atualização devido a alterações na API do golang, omita .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)
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.