Rasgele sayı üreteci düzgün tohum nasıl


161

Go rastgele bir dize oluşturmak için çalışıyorum ve burada şimdiye kadar yazdım kod:

package main

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

func main() {
    fmt.Println(randomString(10))
}

func randomString(l int) string {
    var result bytes.Buffer
    var temp string
    for i := 0; i < l; {
        if string(randInt(65, 90)) != temp {
            temp = string(randInt(65, 90))
            result.WriteString(temp)
            i++
        }
    }
    return result.String()
}

func randInt(min int, max int) int {
    rand.Seed(time.Now().UTC().UnixNano())
    return min + rand.Intn(max-min)
}

Uygulamam çok yavaş. Kullanarak tohumlama timebelirli bir süre için aynı rastgele sayıyı getirir, böylece döngü tekrar tekrar tekrarlar. Kodumu nasıl geliştirebilirim?


2
"İf string (randInt (65,90))! = Temp {" ekstra güvenlik eklemeye çalıştığınıza benziyor, ama hey, işler şans eseri birbiri ardına aynı oluyor. Bunu yaparak aslında entropiyi azaltmış olabilirsiniz.
Jan Matějka

3
Yan not olarak, "time.Now (). UTC (). UnixNano ()" içinde UTC'ye dönüştürmeye gerek yoktur. Unix süresi, UTC olan Epoch'tan beri hesaplanır.
Grzegorz Luczywo

2
Tohumu bir kez, sadece bir kez ve asla bir kereden fazla ayarlamalısınız. uygulamanızın günlerce çalışması durumunda, günde bir kez ayarlayabilirsiniz.
Casperah

Bir kere tohum vermelisin. Ve sanırım "Z" asla görünmeyebilir, sanırım? Bu yüzden başlangıç ​​endeksini ve son endeksi hariç kullanmayı tercih ederim.
Jaehyun Yeom

Yanıtlar:


232

Aynı tohumu her ayarladığınızda, aynı sırayı alırsınız. Elbette eğer tohumu hızlı bir döngüde zamana ayarlıyorsanız, muhtemelen aynı tohumla birçok kez çağıracaksınız.

Sizin durumunuzda, randIntfarklı bir değere sahip olana kadar işlevinizi çağırırken, (Nano tarafından döndürülen şekilde) zamanın değişmesini beklersiniz.

Tüm sözde rasgele kütüphanelere gelince, örneğin belirli bir diziyi (genellikle sadece hata ayıklama ve birim testi için yapılır) yeniden üretmeniz gerekmediği sürece, örneğin programınızı başlatırken tohumu sadece bir kez ayarlamanız gerekir.

Bundan sonra bir Intnsonraki rastgele tamsayıyı almak için çağırmanız yeterlidir .

rand.Seed(time.Now().UTC().UnixNano())Çizgiyi randInt işlevinden ana sistemin başlangıcına taşıyın ve her şey daha hızlı olacaktır.

Ayrıca dize bina basitleştirebilirsiniz düşünüyorum:

package main

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

func main() {
    rand.Seed(time.Now().UTC().UnixNano())
    fmt.Println(randomString(10))
}

func randomString(l int) string {
    bytes := make([]byte, l)
    for i := 0; i < l; i++ {
        bytes[i] = byte(randInt(65, 90))
    }
    return string(bytes)
}

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

Bunu açıkladığınız için teşekkürler, bunun her seferinde tohumlanması gerektiğini düşündüm.
copperMan

13
Ayrıca rand.Seed(...)işleve de ekleyebilirsiniz init(). init()daha önce otomatik olarak çağrılır main(). Eğer çağrıya gerek yoktur Not init()dan main()!
Jabba

2
@ Jabba Doğru. Cevabımı olabildiğince basit ve sorudan çok uzak tutmuyordum, ancak gözleminiz doğru.
Denys Séguret

7
Şimdiye kadar gönderilen anwers'ların hiçbirinin tohumu kriptografik olarak güvenli bir şekilde başlattığını lütfen unutmayın. Uygulamanıza bağlı olarak, bu hiç önemli olmayabilir veya felaket başarısızlığına neden olabilir.
Ingo Blechschmidt

3
@IngoBlechschmidt math/randzaten kriptografik olarak güvenli değil. Bu bir gereklilikse crypto/randkullanılmalıdır.
Duncan Jones

40

İnsanların neden zaman değeri ile tohum aldıklarını anlamıyorum. Bu benim tecrübelerime göre hiç iyi bir fikir olmadı. Örneğin, sistem saati belki nanosaniye cinsinden gösterilirken, sistemin saat hassasiyeti nanosaniye değildir.

Bu program Go oyun alanında çalıştırılmamalıdır, ancak makinenizde çalıştırırsanız ne tür bir hassasiyet beklediğinize dair kaba bir tahmin alırsınız. Yaklaşık 1000000 ns'lik artışlar görüyorum, bu nedenle 1 ms'lik artışlar görüyorum. Kullanılmayan 20 bit entropi. Tüm bu sırada yüksek bitler çoğunlukla sabittir.

Bunun sizin için önemli olduğu derece değişecektir, ancak sadece tohumunuz için crypto/rand.Readas kaynağını kullanarak saat bazlı tohum değerlerinin tuzaklarından kaçınabilirsiniz . Muhtemelen rastgele sayılarınızda aradığınız deterministik olmayan kaliteyi verecektir (gerçek uygulamanın kendisi bir dizi farklı ve deterministik rastgele dizilerle sınırlı olsa bile).

import (
    crypto_rand "crypto/rand"
    "encoding/binary"
    math_rand "math/rand"
)

func init() {
    var b [8]byte
    _, err := crypto_rand.Read(b[:])
    if err != nil {
        panic("cannot seed math/rand package with cryptographically secure random number generator")
    }
    math_rand.Seed(int64(binary.LittleEndian.Uint64(b[:])))
}

Bir yan not olarak ama sorunuzla ilgili olarak. rand.SourceKilitleme kaynağını korumak için maliyeti önlemek için bu yöntemi kullanarak kendi oluşturabilirsiniz . randPaket yardımcı işlevler uygundur ancak aynı zamanda eş zamanlı olarak kullanılan gelen kaynak önlemek için başlık altında kilitleri kullanmak. İhtiyacınız yoksa, kendinizinkini oluşturarak bunu önleyebilirsiniz ve bunu Sourceeşzamanlı olmayan bir şekilde kullanabilirsiniz. Ne olursa olsun, yinelemeler arasında rasgele sayı üretecinizi yeniden kullanmamalısınız, asla bu şekilde kullanılmak üzere tasarlanmamıştır.


6
Bu cevap çok takdir edilmiyor. Özellikle saniyede birkaç kez çalışabilen komut satırı araçları için bu bir zorunluluktur. Thank you
saeedgnu

1
Gerekirse PID ve ana bilgisayar adını / MAC'i karıştırabilirsiniz, ancak RNG'yi kriptografik olarak güvenli bir kaynakla tohumlamanın, birinin PRNG iç durumunu yeniden yapılandırabileceği için şifreli olarak güvenli hale getirmediğine dikkat edin.
Nick T

PID'ler gerçekten rastgele değil. MAC'ler klonlanabilir. Bunları, istenmeyen bir eğim / sapma yaratmayacak şekilde nasıl karıştırırsınız?
John Leidegren

16

sadece posterity için dışarı atmak için: bazen bir başlangıç ​​karakter kümesi dizesi kullanarak rastgele bir dize oluşturmak tercih edilebilir. Dizenin bir insan tarafından manuel olarak girilmesi gerekiyorsa bu yararlıdır; 0, O, 1 ve l hariç olmak kullanıcı hatasını azaltmaya yardımcı olabilir.

var alpha = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789"

// generates a random string of fixed size
func srand(size int) string {
    buf := make([]byte, size)
    for i := 0; i < size; i++ {
        buf[i] = alpha[rand.Intn(len(alpha))]
    }
    return string(buf)
}

ve tohumu tipik olarak bir init()bloğun içine koydum. Burada belgelenmişlerdir: http://golang.org/doc/effective_go.html#init


9
Bildiğim kadarıyla doğru anlamak gibi olması gerek yoktur -1içinde rand.Intn(len(alpha)-1). Bunun nedeni rand.Intn(n)her zaman küçük olan bir sayı döndürmesidir n(başka bir deyişle: sıfırdan n-1kapsayıcıya).
ek bileşeni

2
@snap doğru; aslında, dahil -1in len(alpha)-1numarası 9 sırayla hiç kullanılmamış olduğunu garanti olurdu.
karbokasyon

2
Ayrıca, 0 (sıfır) hariç tutmanın iyi bir fikir olduğuna dikkat edilmelidir, çünkü bayt dilimini bir dizeye döküyorsunuz ve bu da 0'ın boş bir bayt olmasına neden oluyor. Örneğin, ortasında '0' baytı olan bir dosya oluşturmayı deneyin ve ne olduğunu görün.
Eric Lagergren

14

Tamam neden bu kadar karmaşık!

package main

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

func main() {
    rand.Seed( time.Now().UnixNano())
    var bytes int

    for i:= 0 ; i < 10 ; i++{ 
        bytes = rand.Intn(6)+1
        fmt.Println(bytes)
        }
    //fmt.Println(time.Now().UnixNano())
}

Bu distroy koduna dayanıyor ama ihtiyaçlarım için uygun.

Altı tane öldü (Rands Ints 1 =< i =< 6)

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

Yukarıdaki işlev tamamen aynı şeydir.

Umarım bu bilgiler yararlı olmuştur.


Bu, her zaman aynı diziyi, birden çok kez çağrıldığında aynı sırayla döndürecek, bu benim için çok rastgele görünmüyor. Canlı örneği kontrol edin: play.golang.org/p/fHHENtaPv5 3 5 2 5 4 2 5 6 3 1
Thomas Modeneis

8
@ThomasModeneis: Çünkü oyun alanında sahte zaman geçiriyorlar .
ofavre

1
Teşekkürler @ofavre, bu sahte zaman gerçekten ilk başta beni fırlattı.
Jesse Chisholm

1
Aramadan önce tohum vermeniz gerekir rand.Intn(), aksi takdirde programınızı her çalıştırdığınızda her zaman aynı numarayı alırsınız.
Flavio Copes

Bunun için bir sebep var var bytes intmı? Yukarıdaki değişen fark nedir bytes = rand.Intn(6)+1hiç bytes := rand.Intn(6)+1? İkisi de benim için çalışıyor gibi görünüyor, bunlardan biri bir sebepten ötürü en uygun değil mi?
pzkpfw

0

Bu nano saniye, aynı tohumu iki kez alma şansı nedir.
Her neyse, yardım için teşekkürler, işte tüm girdilere dayanan son çözümüm.

package main

import (
    "math/rand"
    "time"
)

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

// generates a random string
func srand(min, max int, readable bool) string {

    var length int
    var char string

    if min < max {
        length = min + rand.Intn(max-min)
    } else {
        length = min
    }

    if readable == false {
        char = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
    } else {
        char = "ABCDEFHJLMNQRTUVWXYZabcefghijkmnopqrtuvwxyz23479"
    }

    buf := make([]byte, length)
    for i := 0; i < length; i++ {
        buf[i] = char[rand.Intn(len(char)-1)]
    }
    return string(buf)
}

// For testing only
func main() {
    println(srand(5, 5, true))
    println(srand(5, 5, true))
    println(srand(5, 5, true))
    println(srand(5, 5, false))
    println(srand(5, 7, true))
    println(srand(5, 10, false))
    println(srand(5, 50, true))
    println(srand(5, 10, false))
    println(srand(5, 50, true))
    println(srand(5, 10, false))
    println(srand(5, 50, true))
    println(srand(5, 10, false))
    println(srand(5, 50, true))
    println(srand(5, 4, true))
    println(srand(5, 400, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
}

1
re: what are the chances of getting the exact the exact same [nanosecond] twice?Mükemmel. Her şey golang çalışma zamanlarının uygulanmasının iç hassasiyetine bağlıdır . Birimler nano-saniye olsa da, en küçük artış mili-saniye veya hatta bir saniye olabilir.
Jesse Chisholm

0

Amacınız sadece rastgele bir sayı sokması oluşturmaksa, o zaman birden fazla işlev çağrısı veya her seferinde tohum sıfırlama ile karmaşıklaştırmanın gereksiz olduğunu düşünüyorum.

En önemli adım, fiilen çalıştırmadan önce tohum işlevini çağırmaktır rand.Init(x). Tohum , varsayılan Kaynağı deterministik bir duruma başlatmak için sağlanan tohum değerini kullanır. Bu nedenle, sözde rasgele sayı üretecine gerçek işlev çağrısından önce çağrılması önerilir.

Rastgele sayılar dizesi oluşturan örnek bir kod

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



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

    var s string
    for i:=0;i<10;i++{
    s+=fmt.Sprintf("%d ",rand.Intn(7))
    }
    fmt.Printf(s)
}

Kullandığım nedeni sprintf'i basit dize biçimlendirme sağlar çünkü.

Ayrıca, rand.Intn(7) Intn , int olarak, [0,7) 'de negatif olmayan bir sahte rasgele sayı döndürür.


0

@ [Denys Séguret] doğru mesaj gönderdi. Ama benim durumumda kodun altında her zaman yeni tohum ihtiyacım var;

Hızlı işlevlere ihtiyacınız varsa. Ben böyle kullanıyorum.


func RandInt(min, max int) int {
    r := rand.New(rand.NewSource(time.Now().UnixNano()))
    return r.Intn(max-min) + min
}

func RandFloat(min, max float64) float64 {
    r := rand.New(rand.NewSource(time.Now().UnixNano()))
    return min + r.Float64()*(max-min)
}

kaynak


-2

Golang api değişikliği nedeniyle küçük güncelleme, lütfen .UTC () 'yi atlayın:

şimdi zamanı(). UTC () .UnixNano () -> zaman.Şimdi (). UnixNano ()

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

func main() {
    rand.Seed(time.Now().UnixNano())
    fmt.Println(randomInt(100, 1000))
}

func randInt(min int, max int) int {
    return min + rand.Intn(max-min)
}
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.