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?

copperMan
fonte
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)
}
Denys Séguret
fonte
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.

John Leidegren
fonte
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

jorelli
fonte
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.

Luviz
fonte
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))
}
RoboTamer
fonte
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).

Capitão Levi
fonte
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

AÇO
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)
}
letanthang
fonte