Bagaimana cara menghasilkan string acak dengan panjang tetap di Go?

300

Saya ingin string acak hanya karakter (huruf besar atau kecil), tanpa angka, di Go. Apa cara tercepat dan termudah untuk melakukan ini?

Anish Shah
sumber
2
@VinceEmigh: Ini topik meta yang membahas pertanyaan dasar. meta.stackoverflow.com/q/274645/395461 Secara pribadi, saya pikir pertanyaan dasar tidak masalah jika ditulis dengan baik dan sesuai topik. Lihatlah jawaban di bawah ini, mereka menggambarkan banyak hal yang akan berguna bagi orang baru untuk pergi. Untuk loop, ketik casting, make (), dll.
Shannon Matthews
2
@Shannon " Pertanyaan ini tidak menunjukkan upaya penelitian apa pun " (jawaban yang sangat tinggi di tautan Anda) - Itulah yang saya maksud. Dia tidak menunjukkan upaya penelitian. Tidak ada usaha sama sekali (upaya, atau bahkan menyatakan bahwa dia mencari online, yang jelas tidak dia lakukan). Meskipun akan bermanfaat bagi orang baru , situs ini tidak berfokus untuk mengajar orang baru. Ini berfokus pada menjawab masalah / pertanyaan pemrograman tertentu, bukan tutorial / panduan. Meskipun dapat digunakan untuk yang terakhir, itu bukan fokus, dan dengan demikian pertanyaan ini harus ditutup. Sebagai gantinya, ini spoonfed /:
Vince Emigh
9
@VinceEmigh Saya mengajukan pertanyaan ini setahun yang lalu. Saya telah mencari online untuk string acak dan membaca dokumen juga. Tapi itu tidak membantu. Jika saya belum menulis dalam pertanyaan, maka itu tidak berarti saya belum meneliti.
Anish Shah

Jawaban:

809

Solusi Paul menyediakan solusi sederhana dan umum.

Pertanyaannya menanyakan "cara tercepat dan termudah" . Mari kita membahas bagian tercepat juga. Kami akan tiba di kode final kami yang tercepat dengan cara berulang-ulang. Benchmarking setiap iterasi dapat ditemukan di akhir jawaban.

Semua solusi dan kode pembandingan dapat ditemukan di Go Playground . Kode di Playground adalah file tes, bukan yang dapat dieksekusi. Anda harus menyimpannya ke dalam file bernama XX_test.godan menjalankannya

go test -bench . -benchmem

Kata Pengantar :

Solusi tercepat bukanlah solusi masuk jika Anda hanya membutuhkan string acak. Untuk itu, solusi Paul sangat sempurna. Ini jika kinerja memang penting. Meskipun 2 langkah pertama ( Bytes dan Remainder ) mungkin merupakan kompromi yang dapat diterima: mereka memang meningkatkan kinerja sebesar 50% (lihat angka pasti di bagian II. Benchmark ), dan mereka tidak meningkatkan kompleksitas secara signifikan.

Karena itu, bahkan jika Anda tidak membutuhkan solusi tercepat, membaca jawaban ini mungkin penuh petualangan dan mendidik.

I. Perbaikan

1. Genesis (Rune)

Sebagai pengingat, solusi umum asli yang kami tingkatkan adalah ini:

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

Jika karakter untuk memilih dari dan merangkai string acak hanya berisi huruf besar dan kecil dari alfabet bahasa Inggris, kita dapat bekerja dengan byte hanya karena huruf alfabet Inggris memetakan ke byte 1-ke-1 dalam pengkodean UTF-8 (yang adalah cara Go menyimpan string).

Jadi alih-alih:

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

kita bisa menggunakan:

var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

Atau lebih baik lagi:

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

Sekarang ini sudah merupakan perbaikan besar: kita bisa mencapainya menjadi const(ada stringkonstanta tetapi tidak ada konstanta slice ). Sebagai keuntungan tambahan, ekspresi len(letters)juga akan menjadi const! (Ekspresi len(s)konstan jika sstring konstan.)

Dan berapa biayanya? Tidak ada sama sekali. strings dapat diindeks yang mengindeks byte-nya, sempurna, persis seperti yang kita inginkan.

Tujuan kami selanjutnya terlihat seperti ini:

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. Sisa

Solusi sebelumnya mendapatkan nomor acak untuk menunjuk surat acak dengan memanggil rand.Intn()delegasi Rand.Intn()mana yang akan didelegasikan Rand.Int31n().

Ini jauh lebih lambat dibandingkan dengan rand.Int63()yang menghasilkan angka acak dengan 63 bit acak.

Jadi kita bisa memanggil rand.Int63()dan menggunakan sisanya setelah membaginya dengan 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)
}

Ini bekerja dan secara signifikan lebih cepat, kerugiannya adalah bahwa probabilitas semua huruf tidak akan persis sama (dengan asumsi rand.Int63()menghasilkan semua angka 63-bit dengan probabilitas yang sama). Meskipun distorsi sangat kecil karena jumlah huruf 52jauh lebih kecil daripada 1<<63 - 1, jadi dalam praktiknya ini baik-baik saja.

Untuk membuat ini lebih mudah dipahami: katakanlah Anda ingin nomor acak dalam kisaran 0..5. Menggunakan 3 bit acak, ini akan menghasilkan angka 0..1dengan probabilitas ganda daripada dari kisaran 2..5. Dengan menggunakan 5 bit acak, angka dalam kisaran 0..1akan muncul dengan 6/32probabilitas dan angka dalam kisaran 2..5dengan 5/32probabilitas yang sekarang lebih dekat ke yang diinginkan. Meningkatkan jumlah bit membuat ini kurang signifikan, ketika mencapai 63 bit, dapat diabaikan.

4. Masking

Membangun pada solusi sebelumnya, kita dapat mempertahankan distribusi huruf yang sama dengan hanya menggunakan banyak bit terendah dari angka acak sebanyak yang diperlukan untuk mewakili jumlah huruf. Jadi misalnya jika kita memiliki 52 huruf, membutuhkan 6 bit untuk mewakilinya: 52 = 110100b. Jadi kita hanya akan menggunakan 6 bit terendah dari angka yang dikembalikan oleh rand.Int63(). Dan untuk menjaga pemerataan surat, kami hanya "menerima" nomor itu jika berada dalam kisaran 0..len(letterBytes)-1. Jika bit terendah lebih besar, kami membuangnya dan meminta nomor acak baru.

Perhatikan bahwa peluang bit terendah untuk lebih besar dari atau sama dengan len(letterBytes)kurang dari 0.5pada umumnya ( 0.25rata-rata), yang berarti bahwa bahkan jika ini yang terjadi, mengulangi kasus "langka" ini mengurangi kemungkinan tidak menemukan barang yang baik. jumlah. Setelah npengulangan, kemungkinan kita masih tidak memiliki indeks yang baik adalah jauh lebih sedikit daripada pow(0.5, n), dan ini hanya perkiraan yang lebih tinggi. Dalam hal 52 huruf kemungkinan 6 bit terendah tidak bagus hanya (64-52)/64 = 0.19; yang artinya misalnya peluang untuk tidak memiliki angka yang baik setelah 10 kali pengulangan 1e-8.

Jadi, inilah solusinya:

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. Masking Ditingkatkan

Solusi sebelumnya hanya menggunakan 6 bit terendah dari 63 bit acak yang dikembalikan oleh rand.Int63(). Ini adalah pemborosan karena mendapatkan bit acak adalah bagian paling lambat dari algoritma kami.

Jika kita memiliki 52 huruf, itu berarti 6 bit kode indeks huruf. Jadi 63 bit acak dapat menunjuk 63/6 = 10indeks huruf yang berbeda. Mari kita gunakan semua 10 itu:

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. Sumber

The Masking Peningkatan ini cukup bagus, tidak banyak yang bisa kita memperbaiki itu. Kita bisa, tetapi tidak sebanding dengan kerumitannya.

Sekarang mari kita cari hal lain untuk diperbaiki. Sumber angka acak.

Ada crypto/randpaket yang menyediakan Read(b []byte)fungsi, jadi kita bisa menggunakannya untuk mendapatkan banyak byte dengan satu panggilan sebanyak yang kita butuhkan. Ini tidak akan membantu dalam hal kinerja karena crypto/randmengimplementasikan generator nomor pseudorandom yang aman secara kriptografis sehingga jauh lebih lambat.

Jadi mari kita tetap berpegang pada math/randpaket. The rand.Randmenggunakan rand.Sourcesebagai sumber bit acak. rand.Sourceadalah antarmuka yang menentukan Int63() int64metode: tepat dan satu-satunya hal yang kami butuhkan dan gunakan dalam solusi terbaru kami.

Jadi kita tidak benar-benar membutuhkan rand.Rand(baik eksplisit atau global, berbagi salah satu randpaket), a rand.Sourcesudah cukup sempurna bagi kita:

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

Juga catatan bahwa solusi terakhir ini tidak mengharuskan Anda untuk menginisialisasi (benih) global Randdari math/randpaket sebagai yang tidak digunakan (dan kami rand.Sourcebenar diinisialisasi / unggulan).

Satu hal lagi yang perlu diperhatikan di sini: paket dokumen math/randnegara:

Sumber default aman untuk digunakan bersamaan oleh beberapa goroutine.

Jadi sumber default lebih lambat dari Sourceyang dapat diperoleh rand.NewSource(), karena sumber default harus memberikan keamanan di bawah akses / penggunaan bersamaan, sementara rand.NewSource()tidak menawarkan ini (dan dengan demikian Sourcedikembalikan oleh lebih mungkin lebih cepat).

7. Memanfaatkan strings.Builder

Semua solusi sebelumnya menghasilkan stringkonten yang pertama kali dibuat dalam slice ( []runedalam Genesis , dan []bytedalam solusi berikutnya), dan kemudian dikonversi menjadi string. Konversi akhir ini harus membuat salinan konten slice, karena stringnilainya tidak berubah, dan jika konversi tidak akan membuat salinan, tidak dapat dijamin bahwa konten string tidak dimodifikasi melalui slice aslinya. Untuk detailnya, lihat Bagaimana mengonversi utf8 string ke [] byte? dan golang: [] byte (string) vs [] byte (* string) .

Go 1.10 diperkenalkan strings.Builder. strings.Buildertipe baru yang bisa kita gunakan untuk membangun konten yang stringmirip dengan bytes.Buffer. Itu melakukannya secara internal menggunakan []byte, dan ketika kita selesai, kita bisa mendapatkan nilai akhir stringmenggunakan ituBuilder.String() metodenya. Tapi apa yang keren di dalamnya adalah ia melakukan ini tanpa melakukan salinan yang baru saja kita bicarakan di atas. Ia berani melakukannya karena irisan byte yang digunakan untuk membangun konten string tidak diekspos, sehingga dijamin tidak ada yang dapat memodifikasinya secara tidak sengaja atau jahat untuk mengubah string "immutable" yang dihasilkan.

Jadi ide kami berikutnya adalah untuk tidak membangun string acak dalam sebuah irisan, tetapi dengan bantuan a strings.Builder, jadi setelah kami selesai, kami dapat memperoleh dan mengembalikan hasilnya tanpa harus membuat salinannya. Ini mungkin membantu dalam hal kecepatan, dan pasti akan membantu dalam hal penggunaan dan alokasi memori.

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

Perhatikan bahwa setelah membuat yang baru strings.Buidler, kami memanggil Builder.Grow()metodenya, memastikannya mengalokasikan irisan internal yang cukup besar (untuk menghindari realokasi saat kami menambahkan huruf acak).

8. "Meniru" strings.Builderdengan paketunsafe

strings.Buildermembangun string di internal []byte, sama seperti yang kita lakukan sendiri. Jadi pada dasarnya melakukannya melalui strings.Builderbeberapa overhead, satu-satunya hal yang kita beralih strings.Builderadalah untuk menghindari penyalinan akhir dari slice.

strings.Buildermenghindari salinan akhir dengan menggunakan paket unsafe:

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

Masalahnya, kita juga bisa melakukan ini sendiri. Jadi idenya di sini adalah untuk kembali ke membangun string acak dalam []byte, tetapi ketika kita selesai, jangan mengubahnya stringuntuk kembali, tetapi lakukan konversi yang tidak aman: dapatkan stringyang menunjuk ke byte slice kami sebagai data string .

Ini adalah bagaimana hal itu dapat dilakukan:

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. Menggunakan rand.Read())

Go 1.7 menambahkan suatu rand.Read()fungsi danRand.Read() metode. Kita harus tergoda untuk menggunakan ini untuk membaca byte sebanyak yang kita butuhkan dalam satu langkah, untuk mencapai kinerja yang lebih baik.

Ada satu "masalah" kecil dengan ini: berapa banyak byte yang kita butuhkan? Kita bisa mengatakan: sebanyak jumlah surat keluaran. Kami akan berpikir ini adalah estimasi atas, karena indeks huruf menggunakan kurang dari 8 bit (1 byte). Tetapi pada titik ini kita sudah melakukan yang lebih buruk (karena mendapatkan bit acak adalah "bagian yang sulit"), dan kita mendapatkan lebih dari yang dibutuhkan.

Juga perhatikan bahwa untuk menjaga distribusi yang sama dari semua indeks huruf, mungkin ada beberapa data acak "sampah" yang tidak dapat kami gunakan, jadi kami akan melewatkan beberapa data, dan dengan demikian berakhir pendek ketika kami memeriksa semua potongan byte. Kita perlu mendapatkan lebih banyak byte acak, "secara rekursif". Dan sekarang kita bahkan kehilangan keuntungan "satu panggilan ke randpaket" ...

Kami dapat "agak" mengoptimalkan penggunaan data acak yang kami peroleh math.Rand(). Kami dapat memperkirakan berapa banyak byte (bit) yang kami butuhkan. 1 huruf membutuhkan letterIdxBitsbit, dan kita perlu nhuruf, jadi kita perlu n * letterIdxBits / 8.0byte yang mengumpulkan. Kami dapat menghitung probabilitas indeks acak tidak dapat digunakan (lihat di atas), jadi kami dapat meminta lebih banyak yang "lebih mungkin" cukup (jika ternyata tidak, kami ulangi prosesnya). Kita dapat memproses byte slice sebagai "bit stream" misalnya, yang kami punya lib pihak ke-3 yang bagus: github.com/icza/bitio(pengungkapan: Saya penulisnya).

Namun kode Benchmark masih menunjukkan kita tidak menang. Kenapa gitu?

Jawaban untuk pertanyaan terakhir adalah karena rand.Read()menggunakan perulangan dan terus memanggil Source.Int63()hingga mengisi irisan yang dilewati. Apa yang dilakukan RandStringBytesMaskImprSrc()solusinya, tanpa buffer perantara, dan tanpa kompleksitas tambahan. Itu sebabnya RandStringBytesMaskImprSrc()tetap di atas takhta. Ya, RandStringBytesMaskImprSrc()gunakan yang tidak disinkronkan rand.Sourceseperti rand.Read(). Namun alasannya masih berlaku; dan yang terbukti jika kita menggunakan Rand.Read()bukan rand.Read()(yang sebelumnya juga tidak disinkronkan).

II Tolok ukur

Baiklah, sudah waktunya untuk membandingkan solusi yang berbeda.

Momen kebenaran:

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

Hanya dengan beralih dari rune ke byte, kami langsung memiliki peningkatan kinerja 24% , dan kebutuhan memori turun menjadi sepertiga .

Menyingkirkan rand.Intn()dan menggunakan rand.Int63()sebagai gantinya memberikan 20% lagi .

Masking (dan mengulangi jika indeks besar) sedikit melambat (karena panggilan pengulangan): -22% ...

Tetapi ketika kita menggunakan semua (atau sebagian besar) dari 63 bit acak (10 indeks dari satu rand.Int63()panggilan): yang mempercepat waktu besar: 3 kali .

Jika kita puas dengan (non-default, baru) rand.Sourcebukan rand.Rand, kita lagi memperoleh 21%.

Jika kita menggunakan strings.Builder, kita memperoleh kecil 3,5% di kecepatan , tapi kami juga mencapai 50% pengurangan penggunaan memori dan alokasi! Itu bagus!

Akhirnya jika kita berani menggunakan paket, unsafebukan strings.Builder, kita lagi mendapatkan 14% yang bagus .

Membandingkan final dengan solusi awal: RandStringBytesMaskImprSrcUnsafe()adalah 6,3 kali lebih cepat daripada RandStringRunes(), menggunakan memori keenam dan alokasi setengah . Misi selesai.

icza
sumber
8
@RobbieV Yup, karena rand.Sourcedigunakan bersama . Solusi yang lebih baik adalah dengan meneruskan rand.Sourceke RandStringBytesMaskImprSrc()fungsi, dan dengan demikian tidak diperlukan penguncian dan karenanya kinerja / efisiensi tidak terpengaruh. Setiap goroutine bisa memiliki sendiri Source.
icza
113
@icza, itu adalah salah satu jawaban terbaik yang saya lihat sejak lama di SO!
astropan,
1
@MikeAtlas: Sebaiknya hindari menggunakan deferketika jelas bahwa Anda tidak membutuhkannya. Lihat grokbase.com/t/gg/golang-nuts/158zz5p42w/…
Zan Lynx
1
@ ZanLynx thx untuk tip; meskipun deferuntuk membuka kunci mutex baik segera sebelum atau setelah memanggil kunci adalah IMO sebagian besar adalah ide yang sangat bagus; Anda dijamin untuk keduanya tidak lupa untuk membuka kunci tetapi juga membuka kunci meskipun dalam fungsi panik yang tidak fatal.
Mike Atlas
1
@RobbieV sepertinya kode ini aman untuk thread / goroutine karena sumber bersama yang mendasarinya sudah merupakan LockedSource yang mengimplementasikan mutex ( golang.org/src/math/rand/rand.rand:25go ).
adityajones
130

Anda bisa menulis kode untuk itu. Kode ini bisa sedikit lebih sederhana jika Anda ingin mengandalkan huruf-huruf yang semuanya byte tunggal ketika dikodekan dalam UTF-8.

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
sumber
30
Jangan lupa tentang rand.Seed (), jika tidak, Anda mendapatkan string yang sama setiap kali pertama kali diluncurkan ... rand.Seed (waktu. Sekarang (). UTC (). UnixNano ())
Evan Lin
2
Penambahan Evan benar, namun ada opsi lain yang serupa: rand.Seed(time.Now().Unix())ataurand.Seed(time.Now().UnixNano())
openwonk
7
Untuk rahasia yang sulit ditebak - kata sandi, kunci kripto, dll-- jangan pernah gunakan math/rand; gunakan crypto/rand(seperti opsi @ Not_A_Golfer 1).
twotwotwo
1
@ EvanLin Apakah ini tidak bisa ditebak? Jika saya harus menyemai generator, maka penyerang bisa menebak waktu saya menyemai dan memprediksi output yang sama yang saya hasilkan.
Matej
4
Perhatikan bahwa jika Anda mencoba program di atas dengan seed, on go playground, itu akan menghasilkan hasil yang sama setiap saat. Saya mencobanya di taman bermain dan setelah beberapa waktu menyadari ini. Kalau tidak, itu bekerja dengan baik untuk saya. Semoga menghemat waktu seseorang :)
Gaurav Sinha
18

Gunakan paket uniuri , yang menghasilkan string seragam (tidak bias) yang aman secara kriptografis.

Penafian: Saya pembuat paket

dchest
sumber
1
Selain itu, penulis, dchest, adalah pengembang yang sangat baik dan telah menghasilkan sejumlah paket kecil yang bermanfaat seperti ini.
Roshambo
16

Dua opsi yang mungkin (tentu saja ada lebih banyak):

  1. Anda dapat menggunakan crypto/randpaket yang mendukung membaca array byte acak (dari / dev / urandom) dan diarahkan untuk pembuatan acak kriptografis. lihat http://golang.org/pkg/crypto/rand/#example_Read . Mungkin lebih lambat dari generasi nomor pseudo-acak normal.

  2. Ambil nomor acak dan hash menggunakan md5 atau sesuatu seperti ini.

Not_a_Golfer
sumber
4

Mengikuti icza'ssolusi yang dijelaskan dengan luar biasa, berikut ini adalah modifikasi yang menggunakan crypto/randalih-alih 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
}

Jika Anda menginginkan solusi yang lebih umum, yang memungkinkan Anda meneruskan potongan byte karakter untuk membuat string keluar, Anda dapat mencoba menggunakan ini:

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

Jika Anda ingin meneruskan sumber keacakan Anda sendiri, akan sepele untuk memodifikasi di atas untuk menerima io.Readeralih - alih menggunakan crypto/rand.

Chris
sumber
2

jika kamu mau angka acak yang aman secara kriptografis , dan rangkaian karakter yang tepat fleksibel (misalnya, base64 baik-baik saja), Anda dapat menghitung dengan tepat berapa panjang karakter acak yang Anda butuhkan dari ukuran output yang diinginkan.

Teks Base 64 1/3 lebih panjang dari base 256. (2 ^ 8 vs 2 ^ 6; 8bits / 6bits = 1,333 rasio)

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
}

Catatan: Anda juga dapat menggunakan RawStdEncoding jika Anda lebih suka + dan / karakter untuk - dan _

Jika Anda ingin hex, basis 16 adalah 2x lebih panjang dari basis 256. (2 ^ 8 vs 2 ^ 4; 8bits / 4bits = rasio 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
}

Namun, Anda bisa memperluas ini ke set karakter yang sewenang-wenang jika Anda memiliki encoder base256 ke baseN untuk set karakter Anda. Anda dapat melakukan perhitungan ukuran yang sama dengan berapa banyak bit yang dibutuhkan untuk mewakili set karakter Anda. Perhitungan rasio untuk rangkaian karakter acak adalah:ratio = 8 / log2(len(charset)) .

Meskipun kedua solusi ini aman, sederhana, harus cepat, dan jangan sia-siakan kolam entropi kripto Anda.

Inilah taman bermain yang menunjukkan bahwa itu berfungsi untuk berbagai ukuran. https://play.golang.org/p/i61WUVR8_3Z

Steven Soroka
sumber
Layak disebutkan bahwa Go Playground selalu mengembalikan nomor acak yang sama, sehingga Anda tidak akan melihat di sana string acak yang berbeda di eksekusi yang berbeda dari kode itu
TPPZ
2
func Rand(n int) (str string) {
    b := make([]byte, n)
    rand.Read(b)
    str = fmt.Sprintf("%x", b)
    return
}
kevin
sumber
Mengapa menghasilkan n * 2 []byte?
M. Rostami
1

Ini cara saya) Gunakan matematika rand atau crypto rand sesuai keinginan.

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
sumber
0

Jika Anda ingin menambahkan beberapa karakter ke kumpulan karakter yang diizinkan, Anda dapat membuat kode berfungsi dengan apa saja yang menyediakan byte acak melalui io.Reader. Di sini kita gunakan 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
sumber
mengapa random % 64perlu?
Sung Cho
2
Karena len(encodeURL) == 64. Jika random % 64tidak dilakukan, randomPosbisa> = 64 dan menyebabkan kepanikan di luar batas saat runtime.
0xcaff
-1
/*
    korzhao
*/

package rand

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

// Doesn't share the rand library globally, reducing lock contention
type Rand struct {
    Seed int64
    Pool *sync.Pool
}

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

// init random number generator
func NewRand() *Rand {
    p := &sync.Pool{New: func() interface{} {
        return rand.New(rand.NewSource(getSeed()))
    },
    }
    mrand := &Rand{
        Pool: p,
    }
    return mrand
}

// get the seed
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)
}

// get a random number
func (s *Rand) Intn(n int) int {
    r := s.getrand()
    defer s.putrand(r)

    return r.Intn(n)
}

//  bulk get random numbers
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 allocs /

korzhao
sumber
Halo! Selamat datang di StackOverflow. Meskipun Anda menambahkan potongan kode, jawaban Anda tidak menyertakan konteks apa pun tentang "cara kerjanya" atau "mengapa hal ini dilakukan". Ingat juga pertanyaan ini diajukan dalam bahasa Inggris sehingga komentar Anda juga harus dalam bahasa Inggris.
Cengiz Can
-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 allocs / op

pengguna10987909
sumber