Comment semer correctement le générateur de nombres aléatoires

160

J'essaie de générer une chaîne aléatoire dans Go et voici le code que j'ai écrit jusqu'à présent:

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

Ma mise en œuvre est très lente. L'amorçage en utilisant timeapporte le même nombre aléatoire pendant un certain temps, de sorte que la boucle se répète encore et encore. Comment puis-je améliorer mon code?

CopperMan
la source
2
Le "if string (randInt (65,90))! = Temp {" semble que vous essayez d'ajouter une sécurité supplémentaire mais bon, les choses se ressemblent les unes après les autres par hasard. En faisant cela, vous réduisez peut-être l'entropie.
Jan Matějka
3
En remarque, il n'est pas nécessaire de convertir en UTC dans "time.Now (). UTC (). UnixNano ()". L'heure Unix est calculée depuis l'époque qui est de toute façon UTC.
Grzegorz Luczywo
2
Vous devez placer la graine une fois, une seule fois et jamais plus d'une fois. Eh bien, au cas où votre application fonctionnerait pendant des jours, vous pouvez la définir une fois par jour.
Casperah
Vous devriez semer une fois. Et je pense que "Z" n'apparaîtra peut-être jamais, je suppose? Je préfère donc utiliser begin index inclusive et end index exclusive.
Jaehyun Yeom

Réponses:

232

Chaque fois que vous définissez la même graine, vous obtenez la même séquence. Donc, bien sûr, si vous définissez la graine sur l'heure dans une boucle rapide, vous l'appellerez probablement plusieurs fois avec la même graine.

Dans votre cas, lorsque vous appelez votre randIntfonction jusqu'à ce que vous ayez une valeur différente, vous attendez que le temps (tel que renvoyé par Nano) change.

Comme pour toutes les bibliothèques pseudo-aléatoires , vous ne devez définir la valeur de départ qu'une seule fois, par exemple lors de l'initialisation de votre programme, sauf si vous avez spécifiquement besoin de reproduire une séquence donnée (ce qui n'est généralement fait que pour le débogage et les tests unitaires).

Après cela, vous appelez simplement Intnpour obtenir le prochain entier aléatoire.

Déplacez la rand.Seed(time.Now().UTC().UnixNano())ligne de la fonction randInt au début de la main et tout sera plus rapide.

Notez également que je pense que vous pouvez simplifier la construction de votre chaîne:

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
la source
Merci d'avoir expliqué cela, j'ai pensé que cela devait être semé à chaque fois.
cuivreMan
13
Vous pouvez également ajouter rand.Seed(...)à la fonction init(). init()est appelé automatiquement avant main(). Notez que vous n'avez pas besoin d'appeler init()depuis main()!
Jabba
2
@Jabba C'est vrai. Je gardais ma réponse aussi simple que possible et pas trop loin de la question, mais votre observation est juste.
Denys Séguret
7
Veuillez noter qu'aucune des réponses publiées jusqu'à présent n'initialise la graine de manière cryptographique sécurisée. En fonction de votre application, cela peut ne pas avoir d'importance du tout ou entraîner une panne catastrophique.
Ingo Blechschmidt
3
@IngoBlechschmidt math/randn'est de toute façon pas sécurisé par cryptographie. Si c'est une exigence, crypto/randdoit être utilisé.
Duncan Jones
39

Je ne comprends pas pourquoi les gens sèment avec une valeur temps. Cela n'a jamais été une bonne idée d'après mon expérience. Par exemple, alors que l'horloge système est peut-être représentée en nanosecondes, la précision d'horloge du système n'est pas en nanosecondes.

Ce programme ne doit pas être exécuté sur le terrain de jeu Go, mais si vous l'exécutez sur votre machine, vous obtenez une estimation approximative du type de précision auquel vous pouvez vous attendre. Je vois des incréments d'environ 1000000 ns, donc des incréments de 1 ms. C'est 20 bits d'entropie qui ne sont pas utilisés. Pendant tout ce temps, les bits hauts sont pour la plupart constants.

Le degré auquel cela compte pour vous variera, mais vous pouvez éviter les pièges des valeurs de départ basées sur l'horloge en utilisant simplement le crypto/rand.Readcomme source pour votre semence. Cela vous donnera cette qualité non déterministe que vous recherchez probablement dans vos nombres aléatoires (même si la mise en œuvre réelle elle-même est limitée à un ensemble de séquences aléatoires distinctes et déterministes).

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

En guise de note secondaire, mais en relation avec votre question. Vous pouvez créer le vôtre en rand.Sourceutilisant cette méthode pour éviter le coût d'avoir des verrous protégeant la source. Les randfonctions utilitaires du package sont pratiques, mais elles utilisent également des verrous sous le capot pour empêcher l'utilisation simultanée de la source. Si vous n'en avez pas besoin, vous pouvez l'éviter en créant le vôtre Sourceet l'utiliser de manière non simultanée. Quoi qu'il en soit, vous ne devriez PAS réensemencer votre générateur de nombres aléatoires entre les itérations, il n'a jamais été conçu pour être utilisé de cette façon.

John Leidegren
la source
5
Cette réponse est très sous-estimée. Spécialement pour les outils de ligne de commande qui peuvent s'exécuter plusieurs fois en une seconde, c'est un must. Merci
saeedgnu
1
Vous pouvez mélanger le PID et le nom d'hôte / MAC si nécessaire, mais sachez que le fait d'amorcer le RNG avec une source cryptographiquement sûre ne le rend pas cryptographiquement sécurisé car quelqu'un peut reconstruire l'état interne du PRNG.
Nick T
Les PID ne sont pas vraiment aléatoires. Les MAC peuvent être clonés. Comment les mélangeriez-vous de manière à ne pas introduire de biais / biais indésirable?
John Leidegren
16

juste pour le jeter pour la postérité: il peut parfois être préférable de générer une chaîne aléatoire en utilisant une chaîne de jeu de caractères initiale. Ceci est utile si la chaîne est censée être saisie manuellement par un humain; l'exclusion de 0, O, 1 et l peut aider à réduire les erreurs de l'utilisateur.

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

et je place généralement la graine à l'intérieur d'un init()bloc. Ils sont documentés ici: http://golang.org/doc/effective_go.html#init

Jorelli
la source
9
Pour autant que je comprends bien, il n'y a pas besoin d'avoir -1en rand.Intn(len(alpha)-1). C'est parce que rand.Intn(n)renvoie toujours un nombre qui est inférieur à n(en d'autres termes: de zéro à n-1inclus).
encliqueter
2
@snap est correct; en fait, inclure le -1in len(alpha)-1aurait garanti que le numéro 9 n'a jamais été utilisé dans la séquence.
carbocation le
2
Il convient également de noter que l'exclusion de 0 (zéro) est une bonne idée car vous convertissez la tranche d'octet en une chaîne, ce qui fait que le 0 devient un octet nul. Par exemple, essayez de créer un fichier avec un octet «0» au milieu et voyez ce qui se passe.
Eric Lagergren
14

OK pourquoi si complexe!

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

Ceci est basé sur le code de Dystroy mais adapté à mes besoins.

C'est die six (rands ints 1 =< i =< 6)

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

La fonction ci-dessus est exactement la même chose.

J'espère que ces informations ont été utiles.

Luviz
la source
Cela renverra tout le temps la même séquence, dans le même ordre si elle est appelée plusieurs fois, cela ne me semble pas très aléatoire. Vérifiez l'exemple en direct: play.golang.org/p/fHHENtaPv5 3 5 2 5 4 2 5 6 3 1
Thomas Modeneis
8
@ThomasModeneis: C'est parce qu'ils simulent du temps dans la cour de récréation.
ofavre
1
Merci @ofavre, ce faux-temps m'a vraiment jeté au début.
Jesse Chisholm
1
Vous devez toujours initialiser avant d'appeler rand.Intn(), sinon vous obtiendrez toujours le même numéro à chaque fois que vous exécuterez votre programme.
Flavio Copes
Une raison pour var bytes int? Quelle est la différence de changer ce qui précède bytes = rand.Intn(6)+1en bytes := rand.Intn(6)+1? Ils semblent tous les deux fonctionner pour moi, est-ce que l'un d'eux est sous-optimal pour une raison quelconque?
pzkpfw
0

Ce sont des nanosecondes, quelles sont les chances d'obtenir la même graine deux fois.
Quoi qu'il en soit, merci pour l'aide, voici ma solution finale basée sur toutes les entré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
la source
1
re: what are the chances of getting the exact the exact same [nanosecond] twice?Excellent. Tout dépend de la précision interne de l' implémentation des runtimes golang. Même si les unités sont en nanosecondes, le plus petit incrément peut être d'une milli-seconde ou même d'une seconde.
Jesse Chisholm
0

Si votre objectif est simplement de générer une piqûre de nombre aléatoire, je pense qu'il n'est pas nécessaire de le compliquer avec plusieurs appels de fonction ou de réinitialiser la graine à chaque fois.

L'étape la plus importante consiste à appeler la fonction d'amorçage une seule fois avant de l'exécuter rand.Init(x). Seed utilise la valeur de départ fournie pour initialiser la source par défaut à un état déterministe. Donc, il serait suggéré de l'appeler une fois avant l'appel de la fonction réelle au générateur de nombres pseudo-aléatoires.

Voici un exemple de code créant une chaîne de nombres aléatoires

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 raison pour laquelle j'ai utilisé Sprintf est qu'il permet un formatage de chaîne simple.

De plus, In rand.Intn(7) Intn renvoie, sous la forme d'un int, un nombre pseudo-aléatoire non négatif dans [0,7).

Capitaine Levi
la source
0

@ [Denys Séguret] a posté correctement. Mais dans mon cas, j'ai besoin d'une nouvelle graine à chaque fois, donc sous le code;

Incase vous avez besoin de fonctions rapides. J'utilise comme ça.


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

la source

ACIER
la source
-2

Petite mise à jour en raison du changement de l'API de Golang, veuillez omettre .UTC ():

c'est l'heure(). UTC () .UnixNano () -> heure.Maintenant (). 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
la source