乱数ジェネレータを正しくシードする方法


160

私はGoでランダムな文字列を生成しようとしています、そしてこれが私がこれまでに書いたコードです:

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

私の実装は非常に遅いです。シードを使用しtimeてシーディングすると、一定の時間、同じ乱数が発生するため、ループが何度も繰り返されます。コードを改善するにはどうすればよいですか?


2
「if string(randInt(65,90))!= temp {」は、セキュリティを追加しようとしているように見えますが、たまたま同じことが次々と発生します。これを行うことで、実際にエントロピーを下げることができます。
yaccz 2013年

2
補足として、「time.Now()。UTC()。UnixNano()」でUTCに変換する必要はありません。Unix時間はとにかくUTCであるエポックから計算されます。
Grzegorz Luczywo

2
シードは1回だけ設定する必要があります。まあ、アプリケーションが何日も実行される場合は、1日に1回設定できます。
キャスペラ2017

あなたは一度種をまくべきです。そして、「Z」は決して現れないかもしれないと思いますか?だから私は開始インデックスを包括的に使用し、終了インデックスを排他的に使用することを好みます。
Jaehyun Yeom

回答:


232

同じシードを設定するたびに、同じシーケンスが得られます。もちろん、高速ループでシードを時刻に設定している場合は、同じシードで何度も呼び出すことになります。

あなたの場合、randInt別の値になるまで関数を呼び出しているので、(Nanoによって返された)時間の変化を待っています。

すべての疑似ランダムライブラリの場合、シードを設定する必要があるのは1回だけです。たとえば、特定のシーケンスを再現する必要がない限り、プログラムを初期化するときなどです(これは通常、デバッグとユニットテストでのみ行われます)。

その後Intn、次のランダムな整数を取得するために呼び出すだけです。

rand.Seed(time.Now().UTC().UnixNano())行をrandInt関数からメインの先頭に移動すると、すべてが速くなります。

また、文字列の作成を簡略化できると思います。

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

説明してくれてありがとう、毎回シードする必要があると思った。
CopperMan 2012

13
rand.Seed(...)関数に追加することもできますinit()init()の前に自動的に呼び出されますmain()。あなたがコールする必要はありませんinit()からmain()
Jabba 2014

2
@ジャバそう。私は答えをできるだけ単純にし、質問からそれほど遠くないようにしましたが、あなたの観察は正しいです。
DenysSéguret14年

7
これまでに投稿された回答は、暗号的に安全な方法でシードを初期化しないことに注意してください。アプリケーションによっては、これがまったく問題にならない場合や、致命的な障害が発生する場合があります。
Ingo Blechschmidt 2016

3
@IngoBlechschmidt math/randは、暗号的に安全ではありません。それが要件である場合は、crypto/rand使用する必要があります。
ダンカンジョーンズ

39

なぜ人々が時間の値をシードしているのか分かりません。これは私の経験では決して良い考えではありませんでした。たとえば、システムクロックはおそらくナノ秒で表されますが、システムのクロック精度はナノ秒ではありません。

このプログラムはGoプレイグラウンドで実行するべきではありませんが、マシンで実行すると、期待できる精度のタイプの大まかな見積もりが得られます。約1000000 nsの増分があるので、1 msの増分です。これは、使用されない20ビットのエントロピーです。その間、上位ビットはほとんど一定です。

これが重要である程度はさまざまですがcrypto/rand.Read、シードのソースとして単にを使用するだけで、クロックベースのシード値の落とし穴を回避できます。これにより、おそらく実際の実装自体が一連の明確で決定論的なランダムシーケンスに制限されている場合でも、乱数で求めている非決定論的な品質が得られます。

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

余談ですが、あなたの質問に関連して。rand.Sourceこの方法を使用して独自に作成し、ソースを保護するロックのコストを回避できます。randパッケージユーティリティ関数は便利であるが、それらはまた、同時に使用されるのソースを防止するために、フードの下でロックを使用します。あなたがそれを必要としないなら、あなたはあなた自身のものを作成することによってそれを避けて、それをSource非並行的な方法で使うことができます。いずれにせよ、反復の間に乱数ジェネレータを再シードするべきではありません。そのように使用するようには設計されていません。


5
この答えは非常に過小評価されています。特に、1秒間に複数回実行される可能性のあるコマンドラインツールの場合、これは必須です。ありがとう
saeedgnu

1
必要に応じてPIDとホスト名/ MACを混在させることができますが、誰かがPRNGの内部状態を再構築できるため、RNGを暗号的に安全なソースでシードしても、暗号的に安全ではないことに注意してください。
Nick T

PIDは実際にはランダムではありません。MACは複製できます。不要なスキュー/バイアスを生じさせない方法でそれらをどのように混ぜますか?
John Leidegren

16

後世のために捨てるだけです。最初の文字セット文字列を使用してランダムな文字列を生成する方が好ましい場合があります。これは、文字列が人間によって手動で入力されることになっている場合に役立ちます。0、O、1、およびlを除外すると、ユーザーエラーを減らすことができます。

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

そして、私は通常、init()ブロック内にシードを設定します。それらはここに文書化されています:http : //golang.org/doc/effective_go.html#init


9
私の知る限り正確に理解し、持っている必要はありません-1ではrand.Intn(len(alpha)-1)。これは、rand.Intn(n)常にn(つまり、ゼロからn-1包括的に)より小さい数を返すためです。
2013年

2
@snapは正しいです。実際には、含む-1ではlen(alpha)-19番を順番に使用されていなかったことを保証しているだろう。
カルボカチオン

2
また、バイトスライスを文字列にキャストしているため、0(ゼロ)を除外することをお勧めします。これにより、0がnullバイトになります。たとえば、中央に「0」バイトを含むファイルを作成して、何が起こるかを確認してください。
Eric Lagergren、2015年

14

なぜ複雑なのか。

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

これはdystroyのコードに基づいていますが、私のニーズに適合しています。

それは6死ぬ(rands ints 1 =< i =< 6

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

上記の関数はまったく同じものです。

この情報がお役に立てば幸いです。


それはいつも同じシーケンスを何度も返しますが、複数回呼び出された場合は非常に同じ順序で行われます。ライブサンプルを確認する:play.golang.org/p/fHHENtaPv5 3 5 2 5 4 2 5 6 3 1
Thomas Modeneis

8
@ThomasModeneis:彼らが遊び場で時間偽っているからです。
ofavre

1
@ofavreに感謝します。その偽の時間が本当に最初に私を投げました。
ジェシーチザム

1
を呼び出す前にシードする必要がありrand.Intn()ます。そうしないと、プログラムを実行するたびに常に同じ番号が取得されます。
Flavio Copes 2017

理由はvar bytes int何ですか?上記bytes = rand.Intn(6)+1をに変更することの違いは何bytes := rand.Intn(6)+1ですか?それらは両方とも私にとってはうまくいくようですが、それらの1つは何らかの理由で最適ではありませんか?
pzkpfw

0

ナノ秒です。同じシードを2回取得する可能性はどのくらいですか。
とにかく、助けてくれてありがとう、ここにすべての入力に基づく私の最終的な解決策があります。

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?すばらしい。すべては、golangランタイムの実装の内部精度に依存します。単位はナノ秒ですが、最小の増分はミリ秒または1秒です。
ジェシーチザム

0

あなたの目的が単に乱数の文字列を生成することであるなら、私はそれを複数の関数呼び出しや毎回シードをリセットすることで複雑にする必要はないと思います。

最も重要なステップは、実際に実行する前にシード関数を一度だけ呼び出すことrand.Init(x)です。シードは、提供されたシード値を使用して、デフォルトのSourceを確定的な状態に初期化します。したがって、疑似乱数ジェネレータへの実際の関数呼び出しの前に一度呼び出すことをお勧めします。

乱数の文字列を作成するサンプルコードは次のとおりです

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

私がSprintfを使用した理由は、単純な文字列フォーマットが可能だからです。

また、In rand.Intn(7) Intnは、[0,7)の非負の疑似乱数をintとして返します。


0

@ [DenysSéguret]が正しく投稿しました。しかし、私の場合、毎回新しいシードが必要なので、コードの下にあります。

クイック機能が必要な場合。このように使っています。


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

ソース


-2

golang apiの変更による小さな更新。.UTC()を省略してください:

今の時間()。UTC(). UnixNano()-> time.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)
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.