web-dev-qa-db-ja.com

Golang乱数ジェネレーターで適切にシードする方法

私は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を使用してシードすると、一定の時間、同じ乱数が発生するため、ループは繰り返し繰り返されます。コードを改善するにはどうすればよいですか?

132
copperMan

同じシードを設定するたびに、同じシーケンスが取得されます。したがって、シードを高速ループ内の時間に設定している場合は、同じシードで何度も呼び出すことになるでしょう。

あなたの場合、異なる値になるまで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)
}
201
Denys Séguret

後世のためにそれを放り投げる:初期の文字セット文字列を使用してランダムな文字列を生成することが望ましい場合があります。これは、人間が文字列を手動で入力することになっている場合に便利です。 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

15
jorelli

なぜそんなに複雑なのでしょう!

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

これは破壊のコードに基づいていますが、私のニーズに合っています。

死ぬ6(rands ints 1 =< i =< 6

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

上記の機能はまったく同じものです。

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

12
Luviz

なぜ人々が時間の価値を与えているのか理解できません。これは私の経験では決して良い考えではありませんでした。たとえば、システムクロックはナノ秒単位で表されますが、システムのクロック精度はナノ秒ではありません。

このプログラム Goプレイグラウンドで実行する必要はありませんが、マシンで実行すると、予想できる精度の種類の大まかな見積もりが得られます。約1000000 nsの増分があるため、1ミリ秒の増分があります。これは、使用されない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を作成し、それを非並行的に使用することで回避できます。とにかく、反復の間に乱数ジェネレーターを再シードするべきではありません。そのように使用されるように設計されていません。

10
John Leidegren

ナノ秒です。同じシードを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))
}
0
RoboTamer

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

最も重要なステップは、実際にRand.Init(x)を実行する前に一度だけシード関数を呼び出すことです。 Seed は、提供されたシード値を使用して、デフォルトの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