190 votes

Comment ensemencer correctement un générateur de nombres aléatoires

J'essaie de générer une chaîne aléatoire en 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. Ensemencement à l'aide de time apporte le même numéro aléatoire pendant un certain temps, de sorte que la boucle itère encore et encore. Comment puis-je améliorer mon code ?

2 votes

Le "if string(randInt(65,90))!=temp {" ressemble à une tentative d'ajouter une sécurité supplémentaire, mais les choses se ressemblent les unes après les autres par hasard. En faisant cela, vous pouvez en fait diminuer l'entropie.

3 votes

Par ailleurs, il n'est pas nécessaire de convertir en UTC dans "time.Now().UTC().UnixNano()". Le temps Unix est calculé depuis Epoch qui est UTC de toute façon.

2 votes

Vous devez définir la graine une fois, une seule fois, et jamais plus d'une fois. Si votre application fonctionne pendant plusieurs jours, vous pouvez la définir une fois par jour.

266voto

dystroy Points 145126

Chaque fois que vous mettez 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, comme vous appelez votre randInt 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 Dans ce cas, vous ne devez définir la graine qu'une seule fois, par exemple lors de l'initialisation de votre programme, à moins que vous n'ayez 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, il suffit d'appeler Intn pour obtenir le prochain entier aléatoire.

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

UnixNano renvoie t comme un temps Unix, le nombre de nanosecondes écoulées depuis le 1er janvier 1970 UTC.

Notez également que je pense que vous pouvez simplifier la construction de vos cordes :

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    rand.Seed(time.Now().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)
}

0 votes

Merci d'avoir expliqué cela, je pensais que cela devait être ensemencé à chaque fois.

20 votes

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() de main() !

2 votes

@Jabba Exact. Je gardais ma réponse aussi simple que possible et pas trop éloignée de la question, mais votre observation est juste.

112voto

John Leidegren Points 21951

Je ne comprends pas pourquoi les gens font des semis avec une valeur temporelle. D'après mon expérience, cela n'a jamais été une bonne idée. Par exemple, alors que l'horloge du système est peut-être représentée en nanosecondes, la précision de l'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 obtiendrez une estimation approximative du type de précision que vous pouvez attendre. Je vois des incréments d'environ 1000000 ns, donc des incréments de 1 ms. Cela représente 20 bits d'entropie qui ne sont pas utilisés. Pendant ce temps, les bits élevés sont généralement constants ! En gros, ~24 bits d'entropie sur une journée, ce qui est très facile à forcer (ce qui peut créer des vulnérabilités).

Le degré d'importance que vous accordez à cet aspect variera, mais vous pouvez éviter les pièges des valeurs de semences basées sur l'horloge en utilisant simplement l'attribut crypto/rand.Read comme source pour votre semence. Il vous donnera cette qualité non déterministe que vous recherchez probablement dans vos nombres aléatoires (même si l'implémentation 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 passant, mais en relation avec votre question. Vous pouvez créer votre propre rand.Source en utilisant cette méthode pour éviter le coût des verrous protégeant la source. Le site rand Les fonctions utilitaires des paquets sont pratiques mais elles utilisent également des verrous sous le capot pour empêcher l'utilisation simultanée de la source. Si vous n'avez pas besoin de cela, vous pouvez l'éviter en créant votre propre fichier Source et de l'utiliser de manière non-concurrente. Quoi qu'il en soit, vous ne devez 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.


Edit : J'ai travaillé dans l'ITAM/SAM et le client que nous avons construit (à l'époque) utilisait un semis basé sur une horloge. Après une mise à jour de Windows, de nombreuses machines du parc de l'entreprise ont redémarré à peu près au même moment. Cela a provoqué une attaque DoS involontaire sur l'infrastructure des serveurs en amont, car le client utilisait le temps de démarrage du système pour semer l'aléatoire et ces machines ont fini par choisir plus ou moins aléatoirement le même créneau horaire pour faire leur rapport. Ils étaient censés répartir la charge sur une période d'environ une heure, mais cela ne s'est pas produit. Seed responsbily !

18voto

jorelli Points 2494

Juste 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 caractères initiale. Ceci est utile si la chaîne est censée être saisie manuellement par un humain ; exclure 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

10 votes

Si j'ai bien compris, il n'est pas nécessaire d'avoir -1 en rand.Intn(len(alpha)-1) . Cela s'explique par le fait que rand.Intn(n) renvoie toujours un nombre inférieur à n (en d'autres termes : de zéro à n-1 inclusivement).

2 votes

@snap a raison ; en fait, en incluant l'élément -1 en len(alpha)-1 aurait garanti que le chiffre 9 ne soit jamais utilisé dans la séquence.

2 votes

Il convient également de noter que l'exclusion du 0 (zéro) est une bonne idée car vous convertissez la tranche d'octets en une chaîne de caractères, 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.

14voto

Luviz Points 31

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

C'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.

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

0 votes

Cela retournera 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

9 votes

Thomas Modeneis : C'est parce qu'ils faux temps dans la cour de récréation.

1 votes

Merci @ofavre, ce faux temps m'a vraiment déstabilisé au début.

1voto

J'ai essayé le programme ci-dessous et j'ai vu une chaîne différente à chaque fois.

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func RandomString(count int){
  rand.Seed(time.Now().UTC().UnixNano()) 
  for(count > 0 ){
    x := Random(65,91)
    fmt.Printf("%c",x)
    count--;
  }
}

func Random(min, max int) (int){
 return min+rand.Intn(max-min) 
}

func main() {
 RandomString(12)
}

Et la sortie sur ma console est

D:\james\work\gox>go run rand.go
JFBYKAPEBCRC
D:\james\work\gox>go run rand.go
VDUEBIIDFQIB
D:\james\work\gox>go run rand.go
VJYDQPVGRPXM

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X