Go'da sabit uzunluklu rastgele bir dize nasıl oluşturulur?


300

Git'te yalnızca rastgele bir karakter dizesi (büyük veya küçük harf) istiyorum, sayı yok. Bunu yapmanın en hızlı ve basit yolu nedir?


2
@VinceEmigh: İşte temel soruları tartışan bir meta konu. meta.stackoverflow.com/q/274645/395461 Şahsen, iyi yazılmış ve konuyla ilgili temel soruların iyi olduğunu düşünüyorum. Aşağıdaki cevaplara bakın, yeni birileri için yararlı olacak bir sürü şeyi gösterirler. Döngüler için döküm, yazın (), vb.
Shannon Matthews

2
@Shannon " Bu soru herhangi bir araştırma çabası göstermiyor " (önce bağlantınızdaki son derece onaylanmış cevap) Araştırma çabası göstermiyor. Hiç çaba yok (bir girişim, hatta çevrimiçi olarak baktığını ve hatta açıkça görmediğini belirten). Her ne kadar yeni biri için yararlı olsa da , bu site yeni insanlara öğretmeye odaklanmamıştır. Öğreticiler / kılavuzlar değil, belirli programlama problemlerini / sorularını cevaplamaya odaklanmıştır. İkincisi için kullanılabilmesine rağmen, odak noktası bu değildir ve bu nedenle bu soru kapatılmalıdır. Bunun yerine, kaşıkla beslendi /:
Vince Emigh

9
@VinceEmigh Bu soruyu bir yıl önce sordum. Çevrimiçi rastgele dizeler aradım ve dokümanlar da okudum. Ama yardımcı olmadı. Soruda yazmamışsam, araştırma yapmadığım anlamına gelmez.
Anish Shah

Yanıtlar:


809

Paul çözüm bir sağlar basit , genel çözüm.

Soru "en hızlı ve en basit yol" istiyor . En hızlı kısmı da ele alalım . Son, en hızlı kodumuza yinelemeli bir şekilde ulaşacağız. Her bir yinelemeyi kıyaslamak cevabın sonunda bulunabilir.

Tüm çözümler ve kıyaslama kodu Go Playground'da bulunabilir . Oyun Alanı'ndaki kod, yürütülebilir bir dosya değil, bir test dosyasıdır. Bu dosyayı adlı bir dosyaya kaydetmeniz XX_test.gove

go test -bench . -benchmem

Önsöz :

Rastgele bir dizeye ihtiyacınız varsa, en hızlı çözüm bir çözüm değildir. Bunun için Paul'ün çözümü mükemmel. Performans önemliyse budur. İlk 2 adım ( Bayt ve Kalan ) kabul edilebilir bir uzlaşma olsa da: performansı% 50 gibi iyileştirirler ( II. Karşılaştırma bölümünde kesin sayılara bakın) ve karmaşıklığı önemli ölçüde artırmazlar.

En hızlı çözüme ihtiyacınız olmasa bile, bu yanıtı okumak maceracı ve eğitici olabilir.

I. İyileştirmeler

1. Yaratılış (Runes)

Bir hatırlatma olarak, geliştirdiğimiz orijinal, genel çözüm şudur:

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

Rastgele dizeden seçilecek ve bir araya getirilecek karakterler İngilizce alfabenin yalnızca büyük ve küçük harflerini içeriyorsa, yalnızca İngilizce alfabe harfleri UTF-8 kodlamasında 1 ile 1 arasındaki baytlarla eşleştiği için baytlarla çalışabiliriz. Go dizeleri nasıl saklar).

Bunun yerine:

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

kullanabiliriz:

var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

Ya da daha iyisi:

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

Şimdi bu zaten büyük bir gelişme: bunu const( stringsabitler var ancak dilim sabitleri yok ) olarak başarabiliriz . Ekstra bir kazanç olarak, ifade len(letters)de bir olacak const! ( Bir dize sabiti len(s)ise ifade ssabittir.)

Ve ne pahasına? Hiçbir şey. strings, hangi baytını indeksler, mükemmel, tam olarak ne istediğimizi endekslenebilir.

Bir sonraki hedefimiz şöyle görünüyor:

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

Önceki çözümler rand.Intn(), Rand.Intn()hangi delegelerin delegelerini çağırarak rastgele bir harf belirtmek için rastgele bir sayı alır Rand.Int31n().

Bu rand.Int63()63 rastgele bit ile rastgele bir sayı üreten ile karşılaştırıldığında çok daha yavaştır .

Böylece rand.Int63(), bölünerek geri kalanını arayabilir ve kullanabiliriz 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)
}

Bu işe yarar ve önemli ölçüde daha hızlı, dezavantaj, tüm harflerin olasılığının tam olarak aynı olmayacağıdır ( rand.Int63()63-bit sayıların tümünü eşit olasılıkla ürettiği varsayılarak ). Harf sayısı 52çok daha küçük olduğu için bozulma son derece küçük olmasına rağmen, 1<<63 - 1pratikte bu gayet iyi.

Bunu daha kolay anlamak için: diyelim ki aralığında rasgele bir sayı istiyorsunuz 0..5. 3 rasgele bit kullanıldığında, 0..1aralıktan iki kat daha fazla olasılık olan sayılar elde edilir 2..5. 5 rastgele bit kullanılarak, dizi numaraları 0..1ile ortaya çıkabilecek 6/32aralığında olasılık ve sayı 2..5ile 5/32yakın arzu için artık olasılık. Bit sayısını artırmak bunu daha az önemli hale getirir, 63 bite ulaştığında önemsizdir.

4. Maskeleme

Önceki çözüme dayanarak, harflerin sayısını temsil etmek için rastgele sayının en düşük bitlerinden sadece çoğunu kullanarak harflerin eşit dağılımını koruyabiliriz. Örneğin biz 52 harf var ise, onu temsil etmek 6 bit gerektirir: 52 = 110100b. Bu nedenle, yalnızca döndürülen sayının en düşük 6 bitini kullanacağız rand.Int63(). Ve harflerin eşit dağılımını sağlamak için, sadece aralığa girerse sayıyı "kabul ederiz" 0..len(letterBytes)-1. En düşük bitler daha büyükse, onu atar ve yeni bir rasgele sayı sorgularız.

En düşük bitlerin eşit veya daha büyük olma şansının genel olarak ( ortalama) len(letterBytes)daha düşük olduğuna dikkat edin, bu durum böyle olsa bile, bu "nadir" durumu tekrarlamanın iyi bir şey bulma şansını azalttığı anlamına gelir. numara. Tekrarlamadan sonra , eşik alma şansımızın iyi bir endekse sahip olmama şansı çok daha azdır ve bu sadece bir üst tahmindir. 52 harf olması durumunda, en düşük 6 bitin iyi olmama ihtimali sadece ; örneğin 10 tekrardan sonra iyi bir sayıya sahip olmama şansı .0.50.25npow(0.5, n)(64-52)/64 = 0.191e-8

İşte çözüm:

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. Maskeleme Geliştirildi

Önceki çözüm yalnızca döndürülen 63 rastgele bitin en düşük 6 bitini kullanır rand.Int63(). Rastgele bitleri almak algoritmamızın en yavaş kısmı olduğu için bu bir israftır.

52 harfimiz varsa, bu 6 bitin harf indeksini kodladığı anlamına gelir. Böylece 63 rastgele bit 63/6 = 10farklı harf indeksleri belirleyebilir. Tüm bu 10'u kullanalım:

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

Geliştirilmiş Maskeleme çok bunun üzerinde artırabilir değil, oldukça iyidir. Yapabilirdik, ama karmaşıklığa değmez.

Şimdi geliştirmek için başka bir şey bulalım. Rasgele sayıların kaynağı.

crypto/randBir Read(b []byte)işlev sağlayan bir paket var , bu yüzden ihtiyacımız olan tek bir çağrı ile bayt almak için kullanabiliriz. Bu, crypto/randkriptografik olarak güvenli bir sözde sayı üretecini uyguladığı için performans açısından yardımcı olmaz, bu yüzden çok daha yavaştır.

Öyleyse math/randpakete yapışalım. A , rastgele bitlerin kaynağı olarak rand.Randkullanır rand.Source. rand.Sourcebir Int63() int64yöntemi belirten bir arayüzdür : tam olarak ve en son çözümümüzde ihtiyaç duyduğumuz ve kullandığımız tek şey.

Yani gerçekten rand.Rand(açık ya da küresel, randpaketin paylaşılan bir tanesine ) ihtiyacımız yok, a rand.Sourcebizim için mükemmel:

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

Ayrıca, bu son çözümün kullanılmadığı Randiçin math/randpaketin genelini başlatmanızı (tohumlamayı) gerektirmediğini (ve bizim rand.Sourceuygun şekilde başlatıldığını / tohumlandığını) unutmayın.

Burada dikkat edilmesi gereken bir şey daha var: paket doc of math/randstates:

Varsayılan Kaynak, birden fazla goroutin tarafından eşzamanlı kullanım için güvenlidir.

Bu nedenle, varsayılan kaynak Sourceelde edilebilecek bir kaynaktan daha yavaştır rand.NewSource(), çünkü varsayılan kaynağın eşzamanlı erişim / kullanım altında güvenlik sağlaması gerekirken rand.NewSource(), bunu sunmaz (ve dolayısıyla Sourcegeri dönenin daha hızlı olma olasılığı daha yüksektir).

7. Kullanımı strings.Builder

Daha önceki tüm çözümler geri bir string, içeriği ilk olarak bir dilim inşa edilmiştir ( []runede Genesis , ve []bytetakip eden çözeltiler içinde) ve daha sonra ilk olarak string. Bu son dönüşüm, dilimin içeriğinin bir kopyasını oluşturmak zorundadır, çünkü stringdeğerler değişmezdir ve dönüşüm bir kopya oluşturmazsa, dizenin içeriğinin orijinal dilimi aracılığıyla değiştirilmemesi garanti edilemez. Ayrıntılar için bkz. Utf8 dizesi [] baytına nasıl dönüştürülür? ve golang: [] bayt (string) vs [] bayt (* string) .

Go 1.10 tanıtıldı strings.Builder. benzer strings.Builderiçerik oluşturmak için kullanabileceğimiz yeni bir tür . Dahili olarak a kullanarak yapar ve bittiğinde, yöntemini kullanarak son değeri elde edebiliriz . Ama içinde güzel olan, yukarıda bahsettiğimiz kopyayı yapmadan bunu yapmasıdır. Dizenin içeriğini oluşturmak için kullanılan bayt dilimi açıkta olmadığından bunu yapmaya cesaret edebilir, bu nedenle üretilen "değişmez" dizeyi değiştirmek için hiç kimsenin istemeden veya kötü amaçlı olarak değiştiremeyeceği garanti edilir.stringbytes.Buffer[]bytestringBuilder.String()

Bu yüzden bir sonraki fikrimiz rastgele bir dizeyi bir dilimde oluşturmak değil, ama a'nın yardımıyla, strings.Builderbittikten sonra, bir kopyasını yapmak zorunda kalmadan sonucu elde edebilir ve geri verebiliriz. Bu, hız açısından yardımcı olabilir ve bellek kullanımı ve tahsisleri konusunda kesinlikle yardımcı olacaktır.

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

Yeni bir tane oluşturduktan sonra strings.Buidler, Builder.Grow()yeterince büyük bir iç dilim ayırdığından emin olarak yöntemini aradığımızı unutmayın (rastgele harfleri eklerken yeniden tahsisleri önlemek için).

8. "Taklit etme" strings.Builderpaketi ileunsafe

strings.Builderipi []bytekendimizde yaptığımız gibi içsel olarak inşa eder . Yani temelde bunu bir strings.Builderek yük ile yapmak, geçiş yaptığımız tek şey strings.Builderdilimin son kopyalanmasını önlemek.

strings.Builderpaketi kullanarak son kopyadan kaçınır unsafe:

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

Mesele şu ki, bunu kendimiz de yapabiliriz. Buradaki fikir, rastgele dizgiyi a'ya geri döndürmektir []byte, ancak işimiz bittiğinde onu stringgeri dönüşe dönüştürmeyin , ancak güvenli olmayan bir dönüşüm yapın: stringdizgi verileri olarak bayt dilimimize bir nokta elde edin .

Bu şekilde yapılabilir:

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

Go 1.7 bir rand.Read()fonksiyon ve Rand.Read()yöntem ekledi . Daha iyi performans elde etmek için bunları bir adımda ihtiyaç duyduğumuz kadar bayt okumak için kullanmaya cazip gelmeliyiz.

Bununla ilgili küçük bir "sorun" var: kaç bayta ihtiyacımız var? Şunu söyleyebiliriz: çıktı harflerinin sayısı. Bir harf endeksi 8 bit'ten (1 byte) daha az kullandığı için bunun bir üst tahmin olduğunu düşünüyoruz. Ama bu noktada zaten daha kötüsünü yapıyoruz (rastgele bitleri almak "zor kısım" olduğu için) ve ihtiyaç duyduğumuzdan daha fazlasını elde ediyoruz.

Ayrıca, tüm harf endekslerinin eşit dağılımını sağlamak için kullanamayacağımız bazı "çöp" rastgele veriler olabileceğini unutmayın, bu nedenle bazı verileri atlayacağız ve böylece tüm bayt dilimi. Daha fazla "yinelemeli" rastgele bayt almamız gerekecekti. Ve şimdi "tek randpaket arama " avantajını bile kaybediyoruz ...

Elde ettiğimiz rastgele verilerin kullanımını "bir şekilde" optimize edebiliriz math.Rand(). Kaç bayta (bit) ihtiyacımız olacağını tahmin edebiliriz. 1 harf letterIdxBitsbit gerektirir ve nharflere ihtiyacımız vardır , yani n * letterIdxBits / 8.0baytların yuvarlanması gerekir . Rastgele bir endeksin kullanılabilir olmama olasılığını hesaplayabiliriz (yukarıya bakınız), bu yüzden "daha muhtemel" olacak daha fazla talep edebiliriz (eğer ortaya çıkmazsa, işlemi tekrarlıyoruz). Bayt dilimini, örneğin güzel bir 3. taraf lib'e sahip olduğumuz bir "bit akışı" olarak işleyebiliriz: github.com/icza/bitio(açıklama: Ben yazarım).

Ancak Benchmark kodu hala kazanmadığımızı gösteriyor. Neden böyle?

Son sorunun cevabı rand.Read(), bir döngü kullanması ve Source.Int63()geçen dilimi doldurana kadar aramaya devam etmesidir. Tam olarak ne RandStringBytesMaskImprSrc()Çözelti yapar olmayan ara tampon ve ilave karmaşıklık olmadan. Bu yüzden RandStringBytesMaskImprSrc()tahtta kalır. Evet, aksine RandStringBytesMaskImprSrc()senkronize edilmemiş bir kullanır . Ancak akıl yürütme hala geçerlidir; ve bunun yerine kullanırsak kanıtlanır (birincisi de senkronize değildir).rand.Sourcerand.Read()Rand.Read()rand.Read()

II. Karşılaştırma

Pekala, farklı çözümleri kıyaslama zamanı.

Gerçek anı:

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

Sadece runiklerden baytlara geçerek, hemen % 24 performans kazancımız olur ve bellek gereksinimi üçte birine düşer .

Kurtulmak rand.Intn()ve rand.Int63()onun yerine kullanmak % 20'lik bir artış sağlar.

Maskeleme (ve büyük endekslerde tekrarlama) biraz yavaşlar (tekrarlama çağrıları nedeniyle): -22% ...

Ancak 63 rastgele bitin (veya bir rand.Int63()çağrının 10 endeksinin) tümünü (veya çoğunu) kullandığımızda : bu büyük zamanı hızlandırır: 3 kez .

rand.SourceBunun yerine ( varsayılan olmayan, yeni) bir çözüm rand.Randbulursak, yine % 21 kazanırız.

Biz kullanmak durumunda strings.Builder, biz küçücük kazanç 3.5% içinde hız , ama biz de elde % 50 bellek kullanımı ve tahsisleri azalma! Bu iyi!

Sonunda paketi kullanmak unsafeyerine cesaret strings.Builderedersek yine güzel bir % 14 kazanırız .

İlk çözeltiye son Karşılaştırılması: RandStringBytesMaskImprSrcUnsafe()bir 6.3 kat daha hızlı daha RandStringRunes()kullanır altıda biri bellek ve birkaç yerleri olarak yarısı . Görev tamamlandı.


8
@RobbieV Yup, çünkü paylaşılan rand.Sourcebir kullanılıyor. Daha iyi bir geçici çözüm rand.Source, RandStringBytesMaskImprSrc()fonksiyona bir geçmektir ve bu şekilde kilitleme gerekmez ve dolayısıyla performans / verimlilik etkilenmez. Her goroutinin kendine ait olabilir Source.
icza

113
@ icza, SO'da uzun zamandır gördüğüm en iyi cevaplardan biri!
astropanik

1
@MikeAtlas: deferİhtiyacınız olmadığı açık olduğunda kullanmaktan kaçınmalısınız . Bkz. Grokbase.com/t/gg/golang-nuts/158zz5p42w/…
Zan Lynx

1
@ZanLynx uç için thx; Her ne kadar deferya hemen önce veya bir kilit çağıran IMO sonra bir nesnenin kilidini kaldıracak çoğunlukla çok iyi bir fikir; hem ölümcül olmayan bir panik orta fonksiyonunda bile kilidini açmayı unutma hem de kilidini açmayı garanti ediyorsun.
Mike Atlas

1
@RobbieV, altta yatan paylaşılan kaynak zaten muteksi uygulayan bir LockedSource ( golang.org/src/math/rand/rand.go:259 ) olduğu için bu kodun / goroutine güvenli olduğu anlaşılıyor .
adityajones

130

Sadece kod yazabilirsiniz. UTF-8'de kodlandığında tüm harflerin tek bayt olmasına güvenmek istiyorsanız bu kod biraz daha basit olabilir.

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

30
Rand.Seed () 'i unutma, aksi halde ilk açılışta aynı dizeyi aldın ... rand.Seed (time.Now (). UTC (). UnixNano ())
Evan Lin

2
Evan'ın eklenmesi doğru, ancak başka benzer seçenekler var: rand.Seed(time.Now().Unix())veyarand.Seed(time.Now().UnixNano())
openwonk

7
Tahmin edilmesi zor bir sır için - bir şifre, bir kripto anahtarı, vb. - asla kullanmayın math/rand; kullanmak crypto/randyerine (Not_A_Golfer seçimine bağlı 1 @ gibi).
twotwotwo

1
@EvanLin Bu tahmin edilemez mi? Jeneratörü tohumlamak zorunda kalırsam, o zaman saldırgan onu tohumladığım zamanı tahmin edebilir ve ürettiğim çıktıyı tahmin edebilir.
Matej

4
Yukarıdaki programı tohumla deniyorsanız, oyun alanında, her zaman aynı sonucu döndüreceğini unutmayın. Oyun alanında deniyordum ve bir süre sonra bunu anladım. Aksi takdirde benim için iyi çalıştı. Umarım zaman tasarrufu sağlar :)
Gaurav Sinha

18

Kriptografik olarak güvenli tek biçimli (tarafsız) dizeler oluşturan uniuri paketini kullanın .

Feragatname: Paketin yazarıyım


1
Bir yana: yazar, dchest, mükemmel bir geliştiricidir ve bunun gibi bir dizi küçük, kullanışlı paket üretmiştir.
Roshambo

16

İki olası seçenek (elbette daha fazlası olabilir):

  1. crypto/randRasgele bayt dizilerini (/ dev / urandom'dan) okumayı destekleyen ve kriptografik rasgele üretime yönelik paketi kullanabilirsiniz . bkz. http://golang.org/pkg/crypto/rand/#example_Read . Yine de normal rasgele sayı üretmekten daha yavaş olabilir.

  2. Rastgele bir sayı alın ve md5 veya bunun gibi bir şey kullanarak hash yapın.


4

icza'sHarika açıklanmış bir çözümün ardından , crypto/randbunun yerine kullanılan bir modifikasyon 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
}

Dizeyi oluşturmak için karakter bayt diliminden geçmenize izin veren daha genel bir çözüm istiyorsanız, bunu kullanmayı deneyebilirsiniz:

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

Kendi rastgelelik kaynağınızdan geçmek istiyorsanız io.Reader, kullanmak yerine yukarıdakileri değiştirmek önemsiz olacaktır crypto/rand.


2

Kriptografik olarak güvenli olmasını istiyorsanız rasgele sayılar ve tam karakter seti esnekse (örneğin, base64 iyidir), istediğiniz çıktı boyutundan rastgele karakterlerin uzunluğunu tam olarak hesaplayabilirsiniz.

Taban 64 metni taban 256'dan 1/3 daha uzundur. (2 ^ 8 vs 2 ^ 6; 8 bit / 6 bit = 1,333 oran)

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
}

Not: + ve / karakterlerini - ve _ olarak tercih ediyorsanız RawStdEncoding'i de kullanabilirsiniz

Onaltılık istiyorsanız, taban 16 taban 256'dan 2 kat daha uzundur. (2 ^ 8 vs 2 ^ 4; 8 bit / 4 bit = 2x oran)

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
}

Ancak, karakter kümeniz için bir base256 - baseN kodlayıcı varsa, bunu herhangi bir rastgele karakter kümesine genişletebilirsiniz. Karakter kümenizi temsil etmek için kaç bitin gerekli olduğu ile aynı boyut hesaplamasını yapabilirsiniz. Herhangi bir rastgele karakter kümesi için oran hesaplaması:) ratio = 8 / log2(len(charset)).

Her iki çözüm de güvenli, basit, hızlı olmalı ve kripto entropi havuzunuzu boşa harcamayın.

İşte her boyutta çalıştığını gösteren oyun alanı. https://play.golang.org/p/i61WUVR8_3Z


Go Playground'un her zaman aynı rastgele sayıyı döndürdüğünden bahsetmeye değer, bu yüzden orada bu kodun farklı yürütmelerinde farklı rastgele dizeler
görmeyeceksiniz

2
func Rand(n int) (str string) {
    b := make([]byte, n)
    rand.Read(b)
    str = fmt.Sprintf("%x", b)
    return
}

Neden n * 2 üretir []byte?
M.Rostami

1

İşte benim yolum) İstediğiniz gibi matematik rand veya kripto rand kullanın.

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

0

İzin verilen karakter havuzunuza birkaç karakter eklemek istiyorsanız, kodun io.Reader aracılığıyla rasgele bayt sağlayan herhangi bir şeyle çalışmasını sağlayabilirsiniz. Burada kullanıyoruz 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
}

neden random % 64gerekli?
Sung Cho

2
Çünkü len(encodeURL) == 64. Eğer random % 64yapılmadıysa, randomPos> = 64 olabilir ve çalışma zamanında sınırların dışında paniğe neden olabilir.
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 alaşımları /


Merhaba! StackOverflow'a hoş geldiniz. Bir kod snippet'i eklemenize rağmen, yanıtınız "nasıl çalıştığı" veya "neden böyle yapıldığı" ile ilgili herhangi bir bağlam içermiyor. Ayrıca, sorunun İngilizce olarak sorulduğunu lütfen unutmayın; bu nedenle yorumlarınız da İngilizce olmalıdır.
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 alaşımları / op

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.