Wie generiere ich eine zufällige Zeichenfolge mit fester Länge in Go?

300

Ich möchte nur eine zufällige Zeichenfolge (Groß- oder Kleinbuchstaben), keine Zahlen in Go. Was ist der schnellste und einfachste Weg, dies zu tun?

Anish Shah
quelle
2
@VinceEmigh: Hier ist ein Meta-Thema, in dem grundlegende Fragen diskutiert werden. meta.stackoverflow.com/q/274645/395461 Persönlich denke ich, dass grundlegende Fragen in Ordnung sind, wenn sie gut geschrieben und themenbezogen sind. Schauen Sie sich die Antworten unten an, sie veranschaulichen eine Reihe von Dingen, die für jemanden nützlich wären, der neu ist. Für Loops geben Sie Casting, make () usw. ein
Shannon Matthews
2
@Shannon " Diese Frage zeigt keinen Forschungsaufwand " (erste hoch bewertete Antwort in Ihrem Link) - Darauf habe ich mich bezogen. Er zeigt keinen Forschungsaufwand. Überhaupt keine Anstrengung (ein Versuch oder sogar die Aussage, dass er online geschaut hat, was er offensichtlich nicht getan hat). Obwohl es für jemanden, der neu ist , nützlich wäre , konzentriert sich diese Seite nicht darauf, neue Leute zu unterrichten. Es konzentriert sich auf die Beantwortung spezifischer Programmierprobleme / -fragen, nicht auf Tutorials / Anleitungen. Obwohl es für letzteres verwendet werden könnte, ist dies nicht der Schwerpunkt, und daher sollte diese Frage geschlossen werden. Stattdessen ist sein Löffel gefüttert /:
Vince Emigh
9
@VinceEmigh Ich habe diese Frage vor einem Jahr gestellt. Ich hatte online nach zufälligen Zeichenfolgen gesucht und auch Dokumente gelesen. Aber es war nicht hilfreich. Wenn ich nicht in die Frage geschrieben habe, heißt das nicht, dass ich nicht recherchiert habe.
Anish Shah

Antworten:

809

Die Lösung von Paul bietet eine einfache , allgemeine Lösung.

Die Frage fragt nach dem "schnellsten und einfachsten Weg" . Lassen Sie uns auch den schnellsten Teil ansprechen . Wir werden iterativ zu unserem endgültigen, schnellsten Code gelangen. Das Benchmarking jeder Iteration finden Sie am Ende der Antwort.

Alle Lösungen und der Benchmarking-Code finden Sie auf dem Go Playground . Der Code auf dem Spielplatz ist eine Testdatei, keine ausführbare Datei. Sie müssen es in einer Datei mit dem Namen speichern XX_test.gound mit ausführen

go test -bench . -benchmem

Vorwort :

Die schnellste Lösung ist keine Lösung, wenn Sie nur eine zufällige Zeichenfolge benötigen. Dafür ist Pauls Lösung perfekt. Dies ist der Fall, wenn die Leistung eine Rolle spielt. Obwohl die ersten beiden Schritte ( Bytes und Rest ) ein akzeptabler Kompromiss sein könnten: Sie verbessern die Leistung um etwa 50% (siehe genaue Zahlen im Abschnitt II. Benchmark ) und erhöhen die Komplexität nicht wesentlich.

Selbst wenn Sie nicht die schnellste Lösung benötigen, kann das Lesen dieser Antwort abenteuerlich und lehrreich sein.

I. Verbesserungen

1. Genesis (Runen)

Zur Erinnerung: Die ursprüngliche, allgemeine Lösung, die wir verbessern, lautet:

func init() {
    rand.Seed(time.Now().UnixNano())
}

var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

func RandStringRunes(n int) string {
    b := make([]rune, n)
    for i := range b {
        b[i] = letterRunes[rand.Intn(len(letterRunes))]
    }
    return string(b)
}

2. Bytes

Wenn die Zeichen, aus denen Sie auswählen und die zufällige Zeichenfolge zusammenstellen können, nur Groß- und Kleinbuchstaben des englischen Alphabets enthalten, können wir nur mit Bytes arbeiten, da die Buchstaben des englischen Alphabets in der UTF-8-Codierung den Bytes 1 zu 1 zugeordnet sind (welche So speichert Go Strings.

Also statt:

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

wir können benutzen:

var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

Oder noch besser:

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

Dies ist bereits eine große Verbesserung: Wir könnten es als a erreichen const(es gibt stringKonstanten, aber keine Slice-Konstanten ). Als zusätzlichen Gewinn wird der Ausdruck len(letters)auch ein const! (Der Ausdruck len(s)ist konstant, wenn ses sich um eine Zeichenfolgenkonstante handelt.)

Und zu welchem ​​Preis? Gar nichts. strings kann indiziert werden, wodurch seine Bytes indiziert werden, perfekt, genau das, was wir wollen.

Unser nächstes Ziel sieht so aus:

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

func RandStringBytes(n int) string {
    b := make([]byte, n)
    for i := range b {
        b[i] = letterBytes[rand.Intn(len(letterBytes))]
    }
    return string(b)
}

3. Rest

Frühere Lösungen erhalten eine Zufallszahl, um einen zufälligen Buchstaben zu bestimmen, indem sie anrufen, an rand.Intn()welche Delegierten Rand.Intn()welche Delegierten senden Rand.Int31n().

Dies ist viel langsamer als bei rand.Int63()einer Zufallszahl mit 63 Zufallsbits.

Wir könnten also einfach rand.Int63()den Rest aufrufen und verwenden, nachdem wir geteilt haben durch len(letterBytes):

func RandStringBytesRmndr(n int) string {
    b := make([]byte, n)
    for i := range b {
        b[i] = letterBytes[rand.Int63() % int64(len(letterBytes))]
    }
    return string(b)
}

Dies funktioniert und ist erheblich schneller. Der Nachteil ist, dass die Wahrscheinlichkeit aller Buchstaben nicht exakt gleich ist (vorausgesetzt, es rand.Int63()werden alle 63-Bit-Zahlen mit gleicher Wahrscheinlichkeit erzeugt). Obwohl die Verzerrung extrem gering ist, da die Anzahl der Buchstaben 52viel, viel kleiner ist als 1<<63 - 1, ist dies in der Praxis vollkommen in Ordnung.

Um dies verständlicher zu machen: Angenommen, Sie möchten eine Zufallszahl im Bereich von 0..5. Unter Verwendung von 3 zufälligen Bits würde dies die Zahlen 0..1mit doppelter Wahrscheinlichkeit als aus dem Bereich erzeugen 2..5. Bei Verwendung von 5 Zufallsbits 0..1würden Zahlen im Bereich mit der 6/32Wahrscheinlichkeit und Zahlen im Bereich 2..5mit der 5/32Wahrscheinlichkeit auftreten, die jetzt näher am gewünschten Wert liegen. Das Erhöhen der Anzahl von Bits macht dies weniger bedeutsam, wenn es 63 Bits erreicht, ist es vernachlässigbar.

4. Maskierung

Aufbauend auf der vorherigen Lösung können wir die gleiche Verteilung der Buchstaben beibehalten, indem wir nur so viele der niedrigsten Bits der Zufallszahl verwenden, wie für die Darstellung der Anzahl der Buchstaben erforderlich sind. Wenn wir zum Beispiel 52 Buchstaben haben, sind 6 Bits erforderlich, um sie darzustellen : 52 = 110100b. Wir werden also nur die niedrigsten 6 Bits der von zurückgegebenen Zahl verwenden rand.Int63(). Und um eine gleichmäßige Verteilung der Buchstaben zu gewährleisten, "akzeptieren" wir die Zahl nur, wenn sie in den Bereich fällt 0..len(letterBytes)-1. Wenn die niedrigsten Bits größer sind, verwerfen wir sie und fragen eine neue Zufallszahl ab.

Beachten Sie, dass die Wahrscheinlichkeit, dass die niedrigsten Bits größer oder gleich sind, len(letterBytes)geringer ist als 0.5im Allgemeinen ( 0.25im Durchschnitt), was bedeutet, dass selbst wenn dies der Fall wäre, die Wiederholung dieses "seltenen" Falls die Wahrscheinlichkeit verringert, kein Gut zu finden Nummer. Nach der nWiederholung ist die Wahrscheinlichkeit, dass wir noch keinen guten Index haben, viel geringer als pow(0.5, n), und dies ist nur eine obere Schätzung. Bei 52 Buchstaben ist die Wahrscheinlichkeit, dass die 6 niedrigsten Bits nicht gut sind, nur (64-52)/64 = 0.19; was zum Beispiel bedeutet, dass die Chance besteht, nach 10 Wiederholungen keine gute Zahl zu haben 1e-8.

Hier ist also die Lösung:

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const (
    letterIdxBits = 6                    // 6 bits to represent a letter index
    letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
)

func RandStringBytesMask(n int) string {
    b := make([]byte, n)
    for i := 0; i < n; {
        if idx := int(rand.Int63() & letterIdxMask); idx < len(letterBytes) {
            b[i] = letterBytes[idx]
            i++
        }
    }
    return string(b)
}

5. Maskierung verbessert

Die vorherige Lösung verwendet nur die niedrigsten 6 Bits der 63 von zurückgegebenen Zufallsbits rand.Int63(). Dies ist eine Verschwendung, da das Abrufen der zufälligen Bits der langsamste Teil unseres Algorithmus ist.

Wenn wir 52 Buchstaben haben, bedeutet dies, dass 6 Bit einen Buchstabenindex codieren. So können 63 zufällige Bits 63/6 = 10unterschiedliche Buchstabenindizes bezeichnen. Verwenden wir alle diese 10:

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const (
    letterIdxBits = 6                    // 6 bits to represent a letter index
    letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
    letterIdxMax  = 63 / letterIdxBits   // # of letter indices fitting in 63 bits
)

func RandStringBytesMaskImpr(n int) string {
    b := make([]byte, n)
    // A rand.Int63() generates 63 random bits, enough for letterIdxMax letters!
    for i, cache, remain := n-1, rand.Int63(), letterIdxMax; i >= 0; {
        if remain == 0 {
            cache, remain = rand.Int63(), letterIdxMax
        }
        if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
            b[i] = letterBytes[idx]
            i--
        }
        cache >>= letterIdxBits
        remain--
    }

    return string(b)
}

6. Quelle

Die verbesserte Maskierung ist ziemlich gut, wir können nicht viel daran verbessern. Wir konnten, aber die Komplexität nicht wert.

Lassen Sie uns jetzt etwas anderes finden, um es zu verbessern. Die Quelle von Zufallszahlen.

Es gibt ein crypto/randPaket, das eine Read(b []byte)Funktion bereitstellt , sodass wir damit mit einem einzigen Aufruf so viele Bytes abrufen können, wie wir benötigen. Dies würde in Bezug auf die Leistung nicht helfen, da crypto/randein kryptografisch sicherer Pseudozufallszahlengenerator implementiert wird, sodass er viel langsamer ist.

Bleiben wir also beim math/randPaket. Das rand.Randverwendet a rand.Sourceals Quelle für zufällige Bits. rand.Sourceist eine Schnittstelle, die eine Int63() int64Methode spezifiziert : genau und das einzige, was wir in unserer neuesten Lösung brauchten und verwendeten.

Wir brauchen also nicht wirklich ein rand.Rand(entweder explizites oder globales, gemeinsam genutztes rand) Paket, a rand.Sourceist perfekt genug für uns:

var src = rand.NewSource(time.Now().UnixNano())

func RandStringBytesMaskImprSrc(n int) string {
    b := make([]byte, n)
    // A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
    for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
        if remain == 0 {
            cache, remain = src.Int63(), letterIdxMax
        }
        if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
            b[i] = letterBytes[idx]
            i--
        }
        cache >>= letterIdxBits
        remain--
    }

    return string(b)
}

Beachten Sie auch, dass Sie bei dieser letzten Lösung nicht das Global Randdes math/randPakets initialisieren (Startwert) müssen , da dieser nicht verwendet wird (und unser Paket rand.Sourceordnungsgemäß initialisiert / Startwert ist).

Eine weitere Sache, die Sie hier beachten sollten: Paket doc of math/randstate:

Die Standardquelle ist für die gleichzeitige Verwendung durch mehrere Goroutinen sicher.

Die Standardquelle ist also langsamer als eine Source, die von erhalten werden kann rand.NewSource(), da die Standardquelle Sicherheit bei gleichzeitigem Zugriff / gleichzeitiger Verwendung rand.NewSource()bieten muss, dies jedoch nicht bietet (und daher ist die Sourcevon ihr zurückgegebene Quelle eher schneller).

7. Verwenden strings.Builder

Alle vorherigen Lösungen geben a zurück, stringdessen Inhalt zuerst in einem Slice ( []runein Genesis und []bytein nachfolgenden Lösungen) erstellt und dann in konvertiert wird string. Bei dieser endgültigen Konvertierung muss eine Kopie des Slice-Inhalts erstellt werden, da die stringWerte unveränderlich sind. Wenn bei der Konvertierung keine Kopie erstellt wird, kann nicht garantiert werden, dass der Inhalt der Zeichenfolge nicht über das ursprüngliche Slice geändert wird. Weitere Informationen finden Sie unter Konvertieren der utf8-Zeichenfolge in [] Byte. und golang: [] Byte (Zeichenfolge) vs [] Byte (* Zeichenfolge) .

Go 1.10 eingeführt strings.Builder. strings.BuilderEin neuer Typ, mit dem wir stringähnliche Inhalte erstellen können bytes.Buffer. Es macht es intern mit a []byte, und wenn wir fertig sind, können wir den endgültigen stringWert mit seiner Builder.String()Methode erhalten. Aber was daran cool ist, ist, dass es dies tut, ohne die Kopie auszuführen, über die wir gerade gesprochen haben. Dies wird gewagt, da das zum Erstellen des Inhalts der Zeichenfolge verwendete Byte-Slice nicht verfügbar ist. Daher wird garantiert, dass niemand es unbeabsichtigt oder böswillig ändern kann, um die erzeugte "unveränderliche" Zeichenfolge zu ändern.

Unsere nächste Idee ist es also, die zufällige Zeichenfolge nicht in einem Slice zu erstellen, sondern mit Hilfe von a strings.Builder. Sobald wir fertig sind, können wir das Ergebnis erhalten und zurückgeben, ohne eine Kopie davon erstellen zu müssen. Dies kann in Bezug auf die Geschwindigkeit hilfreich sein, und es wird definitiv in Bezug auf die Speichernutzung und Zuweisung helfen.

func RandStringBytesMaskImprSrcSB(n int) string {
    sb := strings.Builder{}
    sb.Grow(n)
    // A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
    for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
        if remain == 0 {
            cache, remain = src.Int63(), letterIdxMax
        }
        if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
            sb.WriteByte(letterBytes[idx])
            i--
        }
        cache >>= letterIdxBits
        remain--
    }

    return sb.String()
}

Beachten Sie, dass strings.Buidlerwir nach dem Erstellen einer neuen Builder.Grow()Methode diese Methode aufgerufen haben , um sicherzustellen, dass ein ausreichend großes internes Slice zugewiesen wird (um Neuzuordnungen beim Hinzufügen der zufälligen Buchstaben zu vermeiden).

8. "Nachahmen" strings.Buildermit Paketunsafe

strings.Builderbaut den String in einem internen auf []byte, genau wie wir es selbst getan haben. Wenn Sie dies also über a tun, strings.Builderist der Overhead etwas, auf das wir umgestellt haben strings.Builder, um das endgültige Kopieren des Slice zu vermeiden.

strings.Buildervermeidet die endgültige Kopie durch Verwendung von Paket unsafe:

// String returns the accumulated string.
func (b *Builder) String() string {
    return *(*string)(unsafe.Pointer(&b.buf))
}

Die Sache ist, wir können das auch selbst tun. Die Idee hier ist also, wieder zum Erstellen der zufälligen Zeichenfolge in a zu wechseln. []byteWenn wir fertig sind, konvertieren Sie sie nicht stringin return, sondern führen Sie eine unsichere Konvertierung durch: Erhalten Sie eine, stringdie auf unser Byte-Slice als Zeichenfolgendaten verweist .

So kann es gemacht werden:

func RandStringBytesMaskImprSrcUnsafe(n int) string {
    b := make([]byte, n)
    // A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
    for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
        if remain == 0 {
            cache, remain = src.Int63(), letterIdxMax
        }
        if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
            b[i] = letterBytes[idx]
            i--
        }
        cache >>= letterIdxBits
        remain--
    }

    return *(*string)(unsafe.Pointer(&b))
}

(9. Verwenden rand.Read())

Go 1.7 fügte eine rand.Read()Funktion und eine Rand.Read()Methode hinzu. Wir sollten versucht sein, diese zu verwenden, um in einem Schritt so viele Bytes zu lesen, wie wir benötigen, um eine bessere Leistung zu erzielen.

Hier gibt es ein kleines "Problem": Wie viele Bytes brauchen wir? Wir könnten sagen: so viele wie die Anzahl der ausgegebenen Buchstaben. Wir würden denken, dass dies eine obere Schätzung ist, da ein Buchstabenindex weniger als 8 Bits (1 Byte) verwendet. Aber zu diesem Zeitpunkt geht es uns bereits schlechter (da das Erhalten der zufälligen Bits der "schwierige Teil" ist), und wir bekommen mehr als nötig.

Beachten Sie auch, dass es zur Aufrechterhaltung einer gleichmäßigen Verteilung aller Buchstabenindizes möglicherweise zufällige "Müll" -Daten gibt, die wir nicht verwenden können, sodass wir am Ende einige Daten überspringen und daher kurz werden, wenn wir alle durchlaufen das Byte Slice. Wir müssten weitere zufällige Bytes "rekursiv" erhalten. Und jetzt verlieren wir sogar den randVorteil "Single Call to Package" ...

Wir könnten die Verwendung der Zufallsdaten, aus denen wir gewinnen, "etwas" optimieren math.Rand(). Wir können schätzen, wie viele Bytes (Bits) wir benötigen. 1 Buchstabe benötigt letterIdxBitsBits, und wir brauchen nBuchstaben, also brauchen wir n * letterIdxBits / 8.0Bytes, die aufgerundet werden. Wir können die Wahrscheinlichkeit berechnen, dass ein Zufallsindex nicht verwendet werden kann (siehe oben), sodass wir mehr anfordern können, das "wahrscheinlicher" ausreicht (wenn sich herausstellt, dass dies nicht der Fall ist, wiederholen wir den Vorgang). Wir können das Byte-Slice zum Beispiel als "Bit-Stream" verarbeiten, für den wir eine nette Drittanbieter-Bibliothek haben: github.com/icza/bitio(Offenlegung: Ich bin der Autor).

Aber der Benchmark-Code zeigt immer noch, dass wir nicht gewinnen. Wieso ist es so?

Die Antwort auf die letzte Frage lautet, weil rand.Read()eine Schleife verwendet wird und so lange aufgerufen wird, Source.Int63()bis das übergebene Slice gefüllt ist. Genau das, was die RandStringBytesMaskImprSrc()Lösung tut, ohne den Zwischenpuffer und ohne die zusätzliche Komplexität. Deshalb RandStringBytesMaskImprSrc()bleibt auf dem Thron. Ja, RandStringBytesMaskImprSrc()verwendet im rand.SourceGegensatz zu nicht synchronisiert rand.Read(). Aber die Argumentation gilt immer noch; und was bewiesen ist, wenn wir Rand.Read()statt verwenden rand.Read()(ersteres ist auch nicht synchronisiert).

II. Benchmark

In Ordnung, es ist Zeit für ein Benchmarking der verschiedenen Lösungen.

Moment der Wahrheit:

BenchmarkRunes-4                     2000000    723 ns/op   96 B/op   2 allocs/op
BenchmarkBytes-4                     3000000    550 ns/op   32 B/op   2 allocs/op
BenchmarkBytesRmndr-4                3000000    438 ns/op   32 B/op   2 allocs/op
BenchmarkBytesMask-4                 3000000    534 ns/op   32 B/op   2 allocs/op
BenchmarkBytesMaskImpr-4            10000000    176 ns/op   32 B/op   2 allocs/op
BenchmarkBytesMaskImprSrc-4         10000000    139 ns/op   32 B/op   2 allocs/op
BenchmarkBytesMaskImprSrcSB-4       10000000    134 ns/op   16 B/op   1 allocs/op
BenchmarkBytesMaskImprSrcUnsafe-4   10000000    115 ns/op   16 B/op   1 allocs/op

Allein durch den Wechsel von Runen zu Bytes erzielen wir sofort einen Leistungszuwachs von 24% , und der Speicherbedarf sinkt auf ein Drittel .

Wenn Sie stattdessen loswerden rand.Intn()und verwenden, erhalten Sie rand.Int63()weitere 20% .

Das Maskieren (und Wiederholen bei großen Indizes) verlangsamt sich etwas (aufgrund von Wiederholungsaufrufen): -22% ...

Wenn wir jedoch alle (oder die meisten) der 63 Zufallsbits (10 Indizes aus einem rand.Int63()Aufruf) verwenden, beschleunigt sich dies erheblich: dreimal .

Wenn wir uns rand.Sourcestattdessen mit einem (nicht standardmäßigen, neuen) zufrieden geben rand.Rand, gewinnen wir erneut 21%.

Wenn wir verwenden strings.Builder, gewinnen wir winzige 3,5% an Geschwindigkeit , aber wir haben auch eine 50% ige Reduzierung der Speichernutzung und -zuweisung erreicht! Das ist schön!

Wenn wir es schließlich wagen, das Paket unsafeanstelle von zu verwenden strings.Builder, erhalten wir wieder schöne 14% .

Vergleich der endgültigen mit der ursprünglichen Lösung: RandStringBytesMaskImprSrcUnsafe()ist 6,3-mal schneller als RandStringRunes(), verwendet einen sechsten Speicher und halb so wenige Zuordnungen . Mission erfüllt.

icza
quelle
8
@RobbieV Yup, weil ein Shared rand.Sourceverwendet wird. Eine bessere Problemumgehung wäre die Übergabe von a rand.Sourcean die RandStringBytesMaskImprSrc()Funktion. Auf diese Weise ist keine Verriegelung erforderlich, und daher wird die Leistung / Effizienz nicht beeinträchtigt. Jede Goroutine könnte ihre eigene haben Source.
icza
113
@icza, das ist eine der besten Antworten, die ich seit langem auf SO gesehen habe!
Astropanic
1
@ MikeAtlas: Sollte die Verwendung vermeiden, deferwenn es offensichtlich ist, dass Sie es nicht benötigen. Siehe grokbase.com/t/gg/golang-nuts/158zz5p42w/…
Zan Lynx
1
@ZanLynx Danke für den Tipp; Obwohl deferes meistens eine sehr gute Idee ist, einen Mutex entweder unmittelbar vor oder nach dem Aufrufen einer Sperre zu entsperren . Sie werden garantiert nicht vergessen, zu entsperren, sondern auch zu entsperren, selbst in einer nicht tödlichen Panik-Mid-Funktion.
Mike Atlas
1
@RobbieV Es sieht so aus, als ob dieser Code thread- / goroutine-sicher ist, da die zugrunde liegende gemeinsam genutzte Quelle bereits eine LockedSource ist, die den Mutex implementiert ( golang.org/src/math/rand/rand.go:259 ).
Adityajones
130

Sie können einfach Code dafür schreiben. Dieser Code kann etwas einfacher sein, wenn Sie sich darauf verlassen möchten, dass die Buchstaben bei der Codierung in UTF-8 alle einzelne Bytes sind.

package main

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

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

func randSeq(n int) string {
    b := make([]rune, n)
    for i := range b {
        b[i] = letters[rand.Intn(len(letters))]
    }
    return string(b)
}

func main() {
    rand.Seed(time.Now().UnixNano())

    fmt.Println(randSeq(10))
}
Paul Hankin
quelle
30
Vergessen Sie nicht den rand.Seed (), sonst haben Sie beim ersten Start dieselbe Zeichenfolge ... rand.Seed (time.Now (). UTC (). UnixNano ())
Evan Lin
2
Evans Hinzufügung ist korrekt, es gibt jedoch andere ähnliche Optionen: rand.Seed(time.Now().Unix())oderrand.Seed(time.Now().UnixNano())
openwonk
7
Für ein schwer zu erratendes Geheimnis - ein Passwort, einen Kryptoschlüssel usw. - niemals verwenden math/rand; Verwenden Sie crypto/randstattdessen (wie bei Option 1 von @ Not_A_Golfer).
Zwei
1
@EvanLin Wird das nicht erraten? Wenn ich den Generator säen muss, kann der Angreifer die Zeit erraten, mit der ich ihn säe, und die gleiche Ausgabe vorhersagen, die ich generiere.
Matej
4
Beachten Sie, dass, wenn Sie das oben genannte Programm mit Seed auf dem Spielplatz ausprobieren, immer das gleiche Ergebnis zurückgegeben wird. Ich habe es auf dem Spielplatz ausprobiert und nach einiger Zeit gemerkt. Ansonsten hat es bei mir gut funktioniert. Hoffe, es spart jemandem Zeit :)
Gaurav Sinha
18

Verwenden Sie das Paket uniuri , das kryptografisch sichere einheitliche (unverzerrte) Zeichenfolgen generiert.

Haftungsausschluss: Ich bin der Autor des Pakets

dchest
quelle
1
Nebenbei: Der Autor, dchest, ist ein ausgezeichneter Entwickler und hat eine Reihe kleiner, nützlicher Pakete wie dieses erstellt.
Roshambo
16

Zwei mögliche Optionen (es könnte natürlich mehr geben):

  1. Sie können das crypto/randPaket verwenden, das das Lesen von zufälligen Byte-Arrays (aus / dev / urandom) unterstützt und auf die kryptografische Zufallsgenerierung ausgerichtet ist. Siehe http://golang.org/pkg/crypto/rand/#example_Read . Es könnte jedoch langsamer sein als die normale Erzeugung von Pseudozufallszahlen.

  2. Nehmen Sie eine Zufallszahl und hashen Sie sie mit md5 oder ähnlichem.

Not_a_Golfer
quelle
4

Nach einer icza'swunderbar erklärten Lösung finden Sie hier eine Modifikation, die crypto/randanstelle von verwendet wird math/rand.

const (
    letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" // 52 possibilities
    letterIdxBits = 6                    // 6 bits to represent 64 possibilities / indexes
    letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
)

func SecureRandomAlphaString(length int) string {

    result := make([]byte, length)
    bufferSize := int(float64(length)*1.3)
    for i, j, randomBytes := 0, 0, []byte{}; i < length; j++ {
        if j%bufferSize == 0 {
            randomBytes = SecureRandomBytes(bufferSize)
        }
        if idx := int(randomBytes[j%length] & letterIdxMask); idx < len(letterBytes) {
            result[i] = letterBytes[idx]
            i++
        }
    }

    return string(result)
}

// SecureRandomBytes returns the requested number of bytes using crypto/rand
func SecureRandomBytes(length int) []byte {
    var randomBytes = make([]byte, length)
    _, err := rand.Read(randomBytes)
    if err != nil {
        log.Fatal("Unable to generate random bytes")
    }
    return randomBytes
}

Wenn Sie eine allgemeinere Lösung wünschen, mit der Sie das Stück Zeichenbytes übergeben können, um die Zeichenfolge daraus zu erstellen, können Sie Folgendes verwenden:

// SecureRandomString returns a string of the requested length,
// made from the byte characters provided (only ASCII allowed).
// Uses crypto/rand for security. Will panic if len(availableCharBytes) > 256.
func SecureRandomString(availableCharBytes string, length int) string {

    // Compute bitMask
    availableCharLength := len(availableCharBytes)
    if availableCharLength == 0 || availableCharLength > 256 {
        panic("availableCharBytes length must be greater than 0 and less than or equal to 256")
    }
    var bitLength byte
    var bitMask byte
    for bits := availableCharLength - 1; bits != 0; {
        bits = bits >> 1
        bitLength++
    }
    bitMask = 1<<bitLength - 1

    // Compute bufferSize
    bufferSize := length + length / 3

    // Create random string
    result := make([]byte, length)
    for i, j, randomBytes := 0, 0, []byte{}; i < length; j++ {
        if j%bufferSize == 0 {
            // Random byte buffer is empty, get a new one
            randomBytes = SecureRandomBytes(bufferSize)
        }
        // Mask bytes to get an index into the character slice
        if idx := int(randomBytes[j%length] & bitMask); idx < availableCharLength {
            result[i] = availableCharBytes[idx]
            i++
        }
    }

    return string(result)
}

Wenn Sie Ihre eigene Zufallsquelle weitergeben möchten, ist es trivial, die obigen Angaben zu ändern, um eine zu akzeptieren, io.Readeranstatt sie zu verwenden crypto/rand.

Chris
quelle
2

Wenn Sie kryptografisch sichere Zufallszahlen wünschen und der genaue Zeichensatz flexibel ist (z. B. base64 ist in Ordnung), können Sie aus der gewünschten Ausgabegröße genau die Länge der benötigten Zufallszeichen berechnen.

Der Text der Basis 64 ist 1/3 länger als der der Basis 256. (Verhältnis 2 ^ 8 vs 2 ^ 6; Verhältnis 8 Bit / 6 Bit = 1,333)

import (
    "crypto/rand"
    "encoding/base64"
    "math"
)

func randomBase64String(l int) string {
    buff := make([]byte, int(math.Round(float64(l)/float64(1.33333333333))))
    rand.Read(buff)
    str := base64.RawURLEncoding.EncodeToString(buff)
    return str[:l] // strip 1 extra character we get from odd length results
}

Hinweis: Sie können RawStdEncoding auch verwenden, wenn Sie + und / Zeichen gegenüber - und _ bevorzugen

Wenn Sie hexadezimal wünschen, ist die Basis 16 2x länger als die Basis 256. (2 ^ 8 vs 2 ^ 4; Verhältnis 8 Bits / 4 Bits = 2x)

import (
    "crypto/rand"
    "encoding/hex"
    "math"
)


func randomBase16String(l int) string {
    buff := make([]byte, int(math.Round(float64(l)/2)))
    rand.Read(buff)
    str := hex.EncodeToString(buff)
    return str[:l] // strip 1 extra character we get from odd length results
}

Sie können dies jedoch auf einen beliebigen Zeichensatz erweitern, wenn Sie einen Base256-BaseN-Encoder für Ihren Zeichensatz haben. Sie können dieselbe Größenberechnung mit der Anzahl der Bits durchführen, die zur Darstellung Ihres Zeichensatzes benötigt werden. Die Verhältnisberechnung für einen beliebigen Zeichensatz lautet :) ratio = 8 / log2(len(charset)).

Obwohl beide Lösungen sicher und einfach sind, sollten sie schnell sein und Ihren Krypto-Entropie-Pool nicht verschwenden.

Hier ist der Spielplatz, der zeigt, dass er für jede Größe funktioniert. https://play.golang.org/p/i61WUVR8_3Z

Steven Soroka
quelle
Erwähnenswert ist, dass Go Playground immer dieselbe Zufallszahl zurückgibt, sodass Sie dort keine unterschiedlichen Zufallszeichenfolgen bei unterschiedlichen Ausführungen dieses Codes sehen
TPPZ
2
func Rand(n int) (str string) {
    b := make([]byte, n)
    rand.Read(b)
    str = fmt.Sprintf("%x", b)
    return
}
Kevin
quelle
Warum erzeugt es n * 2 []byte?
M. Rostami
1

Hier ist mein Weg) Verwenden Sie Math Rand oder Crypto Rand, wie Sie möchten.

func randStr(len int) string {
    buff := make([]byte, len)
    rand.Read(buff)
    str := base64.StdEncoding.EncodeToString(buff)
    // Base 64 can be longer than len
    return str[:len]
}
Dima
quelle
0

Wenn Sie bereit sind, Ihrem Pool zulässiger Zeichen einige Zeichen hinzuzufügen, können Sie den Code mit allem arbeiten lassen, das über einen io.Reader zufällige Bytes bereitstellt. Hier verwenden wir crypto/rand.

// len(encodeURL) == 64. This allows (x <= 265) x % 64 to have an even
// distribution.
const encodeURL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"

// A helper function create and fill a slice of length n with characters from
// a-zA-Z0-9_-. It panics if there are any problems getting random bytes.
func RandAsciiBytes(n int) []byte {
    output := make([]byte, n)

    // We will take n bytes, one byte for each character of output.
    randomness := make([]byte, n)

    // read all random
    _, err := rand.Read(randomness)
    if err != nil {
        panic(err)
    }

    // fill output
    for pos := range output {
        // get random item
        random := uint8(randomness[pos])

        // random % 64
        randomPos := random % uint8(len(encodeURL))

        // put into output
        output[pos] = encodeURL[randomPos]
    }

    return output
}
0xcaff
quelle
warum ist random % 64notwendig
Sung Cho
2
Weil len(encodeURL) == 64. Wenn random % 64dies nicht getan wird, randomPoskönnte> = 64 sein und zur Laufzeit eine Panik außerhalb der Grenzen verursachen.
0xcaff
-1
/*
    korzhao
*/

package rand

import (
    crand "crypto/rand"
    "math/rand"
    "sync"
    "time"
    "unsafe"
)

// 不全局共用rand库,减少锁竞争
type Rand struct {
    Seed int64
    Pool *sync.Pool
}

var (
    MRand    = NewRand()
    randlist = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
)

// 初始化随机数发生器
func NewRand() *Rand {
    p := &sync.Pool{New: func() interface{} {
        return rand.New(rand.NewSource(getSeed()))
    },
    }
    mrand := &Rand{
        Pool: p,
    }
    return mrand
}

// 获取种子
func getSeed() int64 {
    return time.Now().UnixNano()
}

func (s *Rand) getrand() *rand.Rand {
    return s.Pool.Get().(*rand.Rand)
}
func (s *Rand) putrand(r *rand.Rand) {
    s.Pool.Put(r)
}

// 获取随机数
func (s *Rand) Intn(n int) int {
    r := s.getrand()
    defer s.putrand(r)

    return r.Intn(n)
}

// 批量获取随机数
func (s *Rand) Read(p []byte) (int, error) {
    r := s.getrand()
    defer s.putrand(r)

    return r.Read(p)
}

func CreateRandomString(len int) string {
    b := make([]byte, len)
    _, err := MRand.Read(b)
    if err != nil {
        return ""
    }
    for i := 0; i < len; i++ {
        b[i] = randlist[b[i]%(62)]
    }
    return *(*string)(unsafe.Pointer(&b))
}

24,0 ns / op 16 B / op 1 weist / zu

korzhao
quelle
Hallo! Willkommen bei StackOverflow. Obwohl Sie ein Code-Snippet hinzugefügt haben, enthält Ihre Antwort keinen Kontext darüber, wie es funktioniert oder warum dies so gemacht wird. Bitte denken Sie auch daran, dass die Frage auf Englisch gestellt wird, sodass Ihre Kommentare auch auf Englisch sein sollten.
Cengiz Can vor
-2
const (
    chars       = "0123456789_abcdefghijkl-mnopqrstuvwxyz" //ABCDEFGHIJKLMNOPQRSTUVWXYZ
    charsLen    = len(chars)
    mask        = 1<<6 - 1
)

var rng = rand.NewSource(time.Now().UnixNano())

// RandStr 返回指定长度的随机字符串
func RandStr(ln int) string {
    /* chars 38个字符
     * rng.Int63() 每次产出64bit的随机数,每次我们使用6bit(2^6=64) 可以使用10次
     */
    buf := make([]byte, ln)
    for idx, cache, remain := ln-1, rng.Int63(), 10; idx >= 0; {
        if remain == 0 {
            cache, remain = rng.Int63(), 10
        }
        buf[idx] = chars[int(cache&mask)%charsLen]
        cache >>= 6
        remain--
        idx--
    }
    return *(*string)(unsafe.Pointer(&buf))
}

BenchmarkRandStr16-8 20000000 68,1 ns / op 16 B / op 1 Allokationen / op

user10987909
quelle