Jak poprawnie zaszczepić generator liczb losowych


160

Próbuję wygenerować losowy ciąg w Go i oto kod, który napisałem do tej pory:

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

Moja implementacja przebiega bardzo wolno. Rozstawianie przy użyciu timeprzynosi tę samą liczbę losową przez pewien czas, więc pętla jest powtarzana wielokrotnie. Jak mogę ulepszyć swój kod?


2
"If string (randInt (65,90))! = Temp {" wygląda na to, że próbujesz dodać dodatkowe zabezpieczenia, ale hej, przez przypadek rzeczy stają się takie same. Robiąc to, możesz faktycznie obniżyć entropię.
yaccz

3
Na marginesie, nie ma potrzeby przeliczania na UTC w „time.Now (). UTC (). UnixNano ()”. Czas uniksowy jest obliczany od Epoki, która i tak jest UTC.
Grzegorz Luczywo

2
Powinieneś zasiać ziarno raz, tylko raz i nigdy więcej niż raz. cóż, jeśli Twoja aplikacja działa przez kilka dni, możesz ustawić ją raz dziennie.
Casperah,

Powinieneś wysiać raz. I myślę, że „Z” może się nigdy nie pojawić, tak myślę? Dlatego wolę używać początku indeksu włącznie i końca indeksu wyłącznego.
Jaehyun Yeom

Odpowiedzi:


232

Za każdym razem, gdy ustawisz to samo ziarno, otrzymasz tę samą sekwencję. Więc oczywiście, jeśli ustawiasz ziarno na czas w szybkiej pętli, prawdopodobnie będziesz go wielokrotnie wywoływać tym samym ziarnem.

W twoim przypadku, gdy wywołujesz swoją randIntfunkcję, aż uzyskasz inną wartość, czekasz, aż czas (zwrócony przez Nano) się zmieni.

Tak jak w przypadku wszystkich bibliotek pseudolosowych , ziarno należy ustawić tylko raz, na przykład podczas inicjowania programu, chyba że konkretnie potrzebujesz odtworzyć daną sekwencję (co jest zwykle wykonywane tylko w celu debugowania i testowania jednostkowego).

Następnie po prostu zadzwoń, Intnaby uzyskać następną losową liczbę całkowitą.

Przenieś rand.Seed(time.Now().UTC().UnixNano())linię z funkcji randInt na początek main i wszystko będzie szybsze.

Zwróć też uwagę, że myślę, że możesz uprościć tworzenie ciągów:

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

Dzięki za wyjaśnienie, pomyślałem, że za każdym razem trzeba to obsiać.
copperMan,

13
Możesz również dodać rand.Seed(...)do funkcji init(). init()jest wywoływana automatycznie przed main(). Pamiętaj, że nie musisz dzwonić init()z main()!
Jabba

2
@Jabba Right. Moja odpowiedź była jak najprostsza i niezbyt daleko od pytania, ale twoja obserwacja jest słuszna.
Denys Séguret

7
Należy pamiętać, że żadne z opublikowanych do tej pory odpowiedzi nie inicjalizuje seeda w sposób bezpieczny kryptograficznie. W zależności od aplikacji może to w ogóle nie mieć znaczenia lub może spowodować katastrofalną awarię.
Ingo Blechschmidt

3
@IngoBlechschmidt i math/randtak nie jest bezpieczne kryptograficznie. Jeśli jest to wymagane, crypto/randnależy użyć.
Duncan Jones

39

Nie rozumiem, dlaczego ludzie wysiewają wartość czasu. Z mojego doświadczenia wynika, że ​​nigdy nie był to dobry pomysł. Na przykład, podczas gdy zegar systemowy może być przedstawiany w nanosekundach, dokładność zegara systemu nie jest nanosekundami.

Ten program nie powinien być uruchamiany na placu zabaw Go, ale jeśli uruchomisz go na swojej maszynie, uzyskasz zgrubne oszacowanie, jakiego rodzaju precyzji możesz się spodziewać. Widzę przyrosty około 1000000 ns, więc przyrosty 1 ms. To 20 bitów entropii, które nie są używane. Przez cały czas wysokie bity są w większości stałe.

Stopień, w jakim ma to dla ciebie znaczenie, będzie różny, ale możesz uniknąć pułapek związanych z wartościami nasion opartymi na zegarze, po prostu używając crypto/rand.Readjako źródła nasion. Da ci to niedeterministyczną jakość, której prawdopodobnie szukasz w swoich liczbach losowych (nawet jeśli sama implementacja jest ograniczona do zestawu odrębnych i deterministycznych losowych sekwencji).

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

Na marginesie, ale w odniesieniu do twojego pytania. Możesz tworzyć własne rand.Sourceza pomocą tej metody, aby uniknąć kosztów posiadania blokad chroniących źródło. Funkcje randnarzędziowe pakietu są wygodne, ale używają również blokad pod maską, aby zapobiec jednoczesnemu używaniu źródła. Jeśli tego nie potrzebujesz, możesz tego uniknąć, tworząc własną Sourcei używaj jej w sposób inny niż współbieżny. Niezależnie od tego, NIE powinieneś przestawiać swojego generatora liczb losowych między iteracjami, nigdy nie został on zaprojektowany do takiego wykorzystania.


5
Ta odpowiedź jest bardzo niedoceniana. Jest to konieczne zwłaszcza w przypadku narzędzi wiersza poleceń, które mogą działać wiele razy w ciągu sekundy. Dziękuję
saeedgnu

1
W razie potrzeby możesz dodać PID i nazwę hosta / MAC, ale uważaj, że umieszczenie RNG w kryptograficznie bezpiecznym źródle nie czyni go kryptograficznie bezpiecznym, ponieważ ktoś może zrekonstruować wewnętrzny stan PRNG.
Nick T,

Identyfikatory PID nie są tak naprawdę przypadkowe. MAC można klonować. Jak byś wymieszał je w sposób, który nie wprowadza niechcianego wypaczenia / uprzedzeń?
John Leidegren

16

tylko po to, aby wyrzucić go dla potomności: czasami lepiej jest wygenerować losowy ciąg przy użyciu początkowego zestawu znaków. Jest to przydatne, jeśli łańcuch ma być wprowadzony ręcznie przez człowieka; wykluczenie 0, O, 1 i l może pomóc w zmniejszeniu błędu użytkownika.

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

i zazwyczaj umieszczam ziarno wewnątrz init()bloku. Są udokumentowane tutaj: http://golang.org/doc/effective_go.html#init


9
O ile dobrze rozumiem, nie ma potrzeby, aby -1w rand.Intn(len(alpha)-1). Dzieje się tak, ponieważ rand.Intn(n)zawsze zwraca liczbę, która jest mniejsza niż n(innymi słowy: od zera do n-1włącznie).
zatrzask

2
@snap jest poprawne; w rzeczywistości włączenie -1in len(alpha)-1gwarantowałoby, że liczba 9 nigdy nie została użyta w sekwencji.
karbokacja

2
Należy również zauważyć, że wykluczenie 0 (zero) jest dobrym pomysłem, ponieważ rzutujesz kawałek bajtu na łańcuch, a to powoduje, że 0 staje się bajtem zerowym. Np. Spróbuj utworzyć plik z bajtem „0” pośrodku i zobacz, co się stanie.
Eric Lagergren

14

OK, dlaczego takie skomplikowane!

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

Jest to oparte na kodzie dystroy, ale dopasowane do moich potrzeb.

It's die six (rands ints 1 =< i =< 6)

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

Powyższa funkcja to dokładnie to samo.

Mam nadzieję, że te informacje były przydatne.


To zwróci cały czas tę samą sekwencję, w tej samej kolejności, jeśli zostanie wywołana wiele razy, która nie wygląda dla mnie zbyt przypadkowo. Zobacz przykład na żywo: play.golang.org/p/fHHENtaPv5 3 5 2 5 4 2 5 6 3 1
Thomas Modeneis

8
@ThomasModeneis: To dlatego, że udają czas na placu zabaw.
ofavre

1
Dzięki @ofavre, ten fałszywy czas naprawdę mnie rzucił.
Jesse Chisholm

1
Nadal musisz rozstawić się przed wywołaniem rand.Intn(), w przeciwnym razie zawsze otrzymasz ten sam numer za każdym razem, gdy uruchomisz program.
Flavio Copes

Jakiś powód var bytes int? Jaka jest różnica w zmianie powyższego bytes = rand.Intn(6)+1na bytes := rand.Intn(6)+1? Oba wydają się działać dla mnie, czy jeden z nich z jakiegoś powodu nie jest optymalny?
pzkpfw

0

To nano sekundy, jakie są szanse na dwukrotne zdobycie tego samego ziarna.
W każdym razie dzięki za pomoc, oto moje końcowe rozwiązanie oparte na wszystkich danych wejściowych.

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?Doskonale. Wszystko zależy od wewnętrznej precyzji wykonania środowiska wykonawczego golang. Mimo że jednostki są nanosekundami, najmniejszy przyrost może wynosić milisekundę lub nawet sekundę.
Jesse Chisholm

0

Jeśli twoim celem jest po prostu wygenerowanie żądła liczby losowej, myślę, że nie jest konieczne komplikowanie go za pomocą wielu wywołań funkcji lub resetowania nasion za każdym razem.

Najważniejszym krokiem jest wywołanie funkcji seed tylko raz przed faktycznym uruchomieniem rand.Init(x). Seed używa podanej wartości inicjatora do zainicjowania domyślnego źródła do stanu deterministycznego. Dlatego sugeruje się wywołanie go raz przed właściwym wywołaniem funkcji generatora liczb pseudolosowych.

Oto przykładowy kod tworzący ciąg liczb losowych

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

Powodem, dla którego użyłem Sprintf, jest to, że umożliwia proste formatowanie ciągów.

Ponadto In rand.Intn(7) Intn zwraca, jako liczbę int, nieujemną liczbę pseudolosową w [0,7).


0

@ [Denys Séguret] wysłał poprawnie. Ale w moim przypadku potrzebuję nowego ziarna za każdym razem stąd poniższy kod;

Jeśli potrzebujesz szybkich funkcji. Używam w ten sposób.


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

źródło


-2

Mała aktualizacja spowodowana zmianą golang api, pomiń .UTC ():

już czas(). UTC () .UnixNano () -> czas.Now (). UnixNano ()

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

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

func randInt(min int, max int) int {
    return min + rand.Intn(max-min)
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.