Wie man den Zufallszahlengenerator richtig aussät

160

Ich versuche, eine zufällige Zeichenfolge in Go zu generieren, und hier ist der Code, den ich bisher geschrieben habe:

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

Meine Implementierung ist sehr langsam. Seeding using timebringt für eine bestimmte Zeit dieselbe Zufallszahl, sodass die Schleife immer wieder wiederholt wird. Wie kann ich meinen Code verbessern?

KupferMan
quelle
2
Der "if string (randInt (65,90))! = Temp {" sieht so aus, als würden Sie versuchen, zusätzliche Sicherheit zu schaffen, aber hey, die Dinge werden zufällig nacheinander gleich. Auf diese Weise können Sie die Entropie tatsächlich senken.
Yaccz
2
Nebenbei bemerkt, es ist nicht erforderlich, in "time.Now (). UTC (). UnixNano ()" in UTC zu konvertieren. Die Unix-Zeit wird seit der Epoche berechnet, die ohnehin UTC ist.
Grzegorz Luczywo
2
Sie sollten den Samen einmal, nur einmal und nie mehr als einmal setzen. Wenn Ihre Anwendung tagelang ausgeführt wird, können Sie sie einmal am Tag festlegen.
Casperah
Sie sollten einmal säen. Und ich denke, "Z" wird vielleicht nie auftauchen, denke ich? Daher bevorzuge ich die Verwendung des Anfangsindex einschließlich und des Endindex exklusiv.
Jaehyun Yeom

Antworten:

232

Jedes Mal, wenn Sie denselben Startwert festlegen, erhalten Sie dieselbe Sequenz. Wenn Sie also den Startwert in einer schnellen Schleife auf die Zeit einstellen, werden Sie ihn wahrscheinlich viele Male mit demselben Startwert aufrufen.

In Ihrem Fall randIntwarten Sie beim Aufrufen Ihrer Funktion, bis Sie einen anderen Wert haben, darauf, dass sich die Zeit (wie von Nano zurückgegeben) ändert.

Wie bei allen Pseudozufallsbibliotheken müssen Sie den Startwert nur einmal festlegen, z. B. beim Initialisieren Ihres Programms, es sei denn, Sie müssen eine bestimmte Sequenz speziell reproduzieren (dies wird normalerweise nur zum Debuggen und Testen von Einheiten durchgeführt).

Danach rufen Sie einfach Intnauf, um die nächste zufällige Ganzzahl zu erhalten.

Bewegen Sie die rand.Seed(time.Now().UTC().UnixNano())Zeile von der Funktion randInt zum Anfang der Hauptfunktion, und alles wird schneller.

Beachten Sie auch, dass Sie meiner Meinung nach das Erstellen von Zeichenfolgen vereinfachen können:

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
quelle
Vielen Dank für die Erklärung, ich dachte, dies muss jedes Mal ausgesät werden.
KupferMan
13
Sie können rand.Seed(...)die Funktion auch erweitern init(). init()wird vorher automatisch aufgerufen main(). Beachten Sie, dass Sie nicht brauchen , um Anruf init()aus main()!
Jabba
2
@ Jabba Richtig. Ich habe meine Antwort so einfach wie möglich und nicht zu weit von der Frage entfernt gehalten, aber Ihre Beobachtung ist richtig.
Denys Séguret
7
Bitte beachten Sie, dass keine der bisher veröffentlichten Antworten den Samen auf kryptografisch sichere Weise initialisiert. Abhängig von Ihrer Anwendung spielt dies möglicherweise keine Rolle oder führt zu einem katastrophalen Ausfall.
Ingo Blechschmidt
3
@IngoBlechschmidt math/randist ohnehin nicht kryptografisch sicher. Wenn dies erforderlich ist, crypto/randsollte verwendet werden.
Duncan Jones
39

Ich verstehe nicht, warum Leute mit einem Zeitwert säen. Dies war meiner Erfahrung nach nie eine gute Idee. Während beispielsweise die Systemuhr möglicherweise in Nanosekunden dargestellt wird, beträgt die Taktgenauigkeit des Systems keine Nanosekunden.

Dieses Programm sollte nicht auf dem Go-Spielplatz ausgeführt werden. Wenn Sie es jedoch auf Ihrem Computer ausführen, erhalten Sie eine grobe Schätzung der zu erwartenden Genauigkeit. Ich sehe Inkremente von ungefähr 1000000 ns, also Inkremente von 1 ms. Das sind 20 Bit Entropie, die nicht verwendet werden. Währenddessen sind die hohen Bits meist konstant.

Der Grad, in dem dies für Sie von Bedeutung ist, variiert, aber Sie können Fallstricke von uhrbasierten Startwerten vermeiden, indem Sie einfach die crypto/rand.Readals Quelle für Ihren Startwert verwenden . Sie erhalten die nicht deterministische Qualität, nach der Sie wahrscheinlich in Ihren Zufallszahlen suchen (selbst wenn die tatsächliche Implementierung selbst auf eine Reihe unterschiedlicher und deterministischer Zufallssequenzen beschränkt ist).

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

Als Randnotiz aber in Bezug auf Ihre Frage. rand.SourceMit dieser Methode können Sie eigene erstellen , um die Kosten für Sperren zum Schutz der Quelle zu vermeiden. Die randFunktionen des Paketdienstprogramms sind praktisch, verwenden jedoch auch Schlösser unter der Haube, um zu verhindern, dass die Quelle gleichzeitig verwendet wird. Wenn Sie das nicht benötigen, können Sie es vermeiden, indem Sie Ihre eigenen erstellen Sourceund diese nicht gleichzeitig verwenden. Unabhängig davon sollten Sie Ihren Zufallszahlengenerator zwischen den Iterationen NICHT neu säen, er wurde nie für diese Verwendung entwickelt.

John Leidegren
quelle
5
Diese Antwort wird sehr unterschätzt. Insbesondere für Befehlszeilentools, die in einer Sekunde mehrmals ausgeführt werden können, ist dies ein Muss. Vielen Dank
saeedgnu
1
Sie können bei Bedarf die PID und den Hostnamen / MAC einmischen. Beachten Sie jedoch, dass das Seeding des RNG mit einer kryptografisch sicheren Quelle nicht kryptografisch sicher ist, da jemand den internen PRNG-Status rekonstruieren kann.
Nick T
PIDs sind nicht wirklich zufällig. MACs können geklont werden. Wie würden Sie sie so mischen, dass keine unerwünschten Verzerrungen auftreten?
John Leidegren
16

Nur um es für die Nachwelt wegzuwerfen: Manchmal kann es vorzuziehen sein, eine zufällige Zeichenfolge mit einer anfänglichen Zeichensatzzeichenfolge zu generieren. Dies ist nützlich, wenn die Zeichenfolge von einem Menschen manuell eingegeben werden soll. Das Ausschließen von 0, O, 1 und l kann dazu beitragen, Benutzerfehler zu reduzieren.

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

und ich setze normalerweise den Samen innerhalb eines init()Blocks. Sie sind hier dokumentiert: http://golang.org/doc/effective_go.html#init

Jorelli
quelle
9
Soweit ich das richtig verstehen, gibt es keine Notwendigkeit, -1in rand.Intn(len(alpha)-1). Dies liegt daran, dass rand.Intn(n)immer eine Zahl zurückgegeben wird, die kleiner als ist n(mit anderen Worten: von Null bis n-1einschließlich).
Snap
2
@snap ist korrekt; in der Tat hätte das Einschließen des -1In len(alpha)-1garantiert, dass die Nummer 9 nie in der Sequenz verwendet wurde.
Carbokation
2
Es sollte auch beachtet werden, dass das Ausschließen von 0 (Null) eine gute Idee ist, da Sie das Byte-Slice in eine Zeichenfolge umwandeln und dadurch die 0 zu einem Null-Byte wird. Versuchen Sie beispielsweise, eine Datei mit einem '0'-Byte in der Mitte zu erstellen, und sehen Sie, was passiert.
Eric Lagergren
14

OK warum so komplex!

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

Dies basiert auf dem Code des Dystroy, ist aber für meine Bedürfnisse geeignet.

Es ist die sechs (Rand Ints 1 =< i =< 6)

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

Die obige Funktion ist genau das gleiche.

Ich hoffe, diese Informationen waren von Nutzen.

Luviz
quelle
Das wird immer die gleiche Sequenz zurückgeben, in der gleichen Reihenfolge, wenn sie mehrmals aufgerufen wird, was für mich nicht sehr zufällig aussieht. Überprüfen Sie Live-Beispiel: play.golang.org/p/fHHENtaPv5 3 5 2 5 4 2 5 6 3 1
Thomas Modeneis
8
@ ThomasModeneis: Das liegt daran, dass sie Zeit auf dem Spielplatz vortäuschen .
ofavre
1
Danke @ofavre, diese Fälschung hat mich zuerst wirklich umgehauen.
Jesse Chisholm
1
Sie müssen vor dem Aufruf noch einen Startwert festlegen rand.Intn(), andernfalls erhalten Sie immer die gleiche Nummer, wenn Sie Ihr Programm ausführen.
Flavio Copes
Irgendein Grund dafür var bytes int? Was ist der Unterschied der oben auf sich ändernde bytes = rand.Intn(6)+1zu bytes := rand.Intn(6)+1? Sie scheinen beide für mich zu arbeiten. Ist einer von ihnen aus irgendeinem Grund nicht optimal?
pzkpfw
0

Es sind Nanosekunden, wie hoch sind die Chancen, zweimal denselben Samen zu bekommen?
Wie auch immer, danke für die Hilfe, hier ist meine Endlösung, die auf allen Eingaben basiert.

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
quelle
1
re: what are the chances of getting the exact the exact same [nanosecond] twice?Ausgezeichnet. Es hängt alles von der internen Genauigkeit der Implementierung der Golang-Laufzeiten ab. Obwohl die Einheiten Nanosekunden sind, kann das kleinste Inkrement eine Millisekunde oder sogar eine Sekunde sein.
Jesse Chisholm
0

Wenn Sie nur einen Stich einer Zufallszahl erzeugen möchten, ist es meines Erachtens nicht erforderlich, ihn jedes Mal durch mehrere Funktionsaufrufe oder das Zurücksetzen des Startwerts zu komplizieren.

Der wichtigste Schritt besteht darin, die Seed-Funktion nur einmal aufzurufen, bevor sie tatsächlich ausgeführt wird rand.Init(x). Seed verwendet den angegebenen Seed-Wert, um die Standardquelle in einen deterministischen Zustand zu initialisieren. Es wird daher empfohlen, es einmal vor dem eigentlichen Funktionsaufruf des Pseudozufallszahlengenerators aufzurufen.

Hier ist ein Beispielcode, der eine Folge von Zufallszahlen erstellt

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

Der Grund, warum ich Sprintf verwendet habe, ist, dass es eine einfache Formatierung von Zeichenfolgen ermöglicht.

Außerdem gibt In rand.Intn(7) Intn als int eine nicht negative Pseudozufallszahl in [0,7] zurück.

Kapitän Levi
quelle
0

@ [Denys Séguret] hat richtig gepostet. Aber in meinem Fall brauche ich jedes Mal neuen Samen, daher unter dem Code;

Für den Fall, dass Sie schnelle Funktionen benötigen. Ich benutze so.


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

Quelle

STAHL
quelle
-2

Kleines Update aufgrund einer Änderung der Golang-API, bitte .UTC () weglassen:

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
quelle