rand.Seedが非推奨になったGo1.20以降の乱数シードの設定方法
Go1.20以降、rand.Seed()
で乱数を初期化する方法が非推奨となりワーニング(warning)が出力されるようになりました。この記事では、新たに推奨されているrand.New()
を用いた乱数シードの設定方法について解説します。
rand.Seedで警告表示(ワーニング)
私は普段Visual Studio Code(VSCode)でコーディングしています。VSCodeでコーディングしていると、rand.Seed()
の行に以下のようなワーニング(warning)が出力されるようになりました。
上記の警告の一部の行を抜粋したのが以下になります。
rand.Seed is deprecated: As of Go 1.20 there is no reason to call Seed with a random value. Programs that call Seed with a known value to get a specific sequence of results should use New(NewSource(seed)) to obtain a local random generator.
メッセージを読むと、「Go1.20からはSeed
を呼ばずに、New(NewSource(seed))
を使え」ということみたいです。
この記事では、rand.Seed()
の代替手段のrand.New(NerSource(seed))
について解説します。
rand.New(NewSource(Seed))
を使った乱数発生
rand.New(rand.NewSource(seed))
では、乱数ジェネレータを作成して乱数を発生させます。
以下、seedを値を指定して設定する場合と、time関数で設定する方法の2つを紹介します。
seedを設定する場合
rand.New
で乱数ジェネレータを作成し、作成したジェネレータを使って乱数を発生させます(r.Intn
の部分)。
このように、シードを直接与えることで毎回同じ乱数列を発生できます。直接指定する方法は、主に「乱数の再現性」を確保したい場合に利用します。
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
var r *rand.Rand
r = rand.New(rand.NewSource(42))
fmt.Println(r.Intn(10))
}
time関数を利用して乱数を発生させる場合
毎回、乱数のパターンを変更したい場合によく使われる手法が、time関数を利用する方法です。
rand.New()
でtime
関数を利用する場合は、以下のように記述します。これで、現時刻を使った乱数シードの初期化を行うことができます。
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
var r *rand.Rand
r = rand.New(rand.NewSource(time.Now().UnixNano()))
fmt.Println(r.Intn(10))
}
Goのrand.Randの関数の一覧
rand.New()
を使って、乱数発生器のオブジェクトを生成したら、生成したオブジェクトを使って乱数の発生を行います。以下は、rand.Rand
で発生できる乱数の一覧(関数一覧)です。
関数名 | 説明 |
Float32() | 半開区間 [0.0,1.0) の擬似乱数を float32 として返す |
Float64() | 半開区間 [0.0,1.0) の擬似乱数を float64 として返す |
Int() | 負でない疑似乱数 int を返す |
Intn(n int) | 半開区間 [0,n) の非負の擬似乱数を int として返す |
Int31() | 非負の擬似ランダム 31 ビット整数を int32 として返す |
Int31n(n int32) | 半開区間 [0,n) の非負の擬似乱数を int32 として返す |
Int63() | 負でない疑似ランダム 63 ビット整数を int64 として返す |
Int63n(n int64) | 半開区間 [0,n) の非負の擬似乱数を int64 として返す |
NormFloat64() | 標準正規分布 (平均 = 0、標準偏差 = 1) で、-math.MaxFloat64 から +math.MaxFloat64 までの範囲の正規分布した float64 を返す |
Perm(n int) | n 個の整数のスライスとして、半開区間 [0,n) の整数の擬似ランダム順列を返す |
Shuffle(n int, swap func(i, j int)) | 要素の順序を擬似ランダム化する |
Uint32() | 疑似ランダム 32 ビット値を uint32 として返す |
Uint64() | 疑似ランダム 64 ビット値を uint64 として返す |
基本的に、発生させたい乱数の種類に応じて関数を呼び出します。rand.New()
で生成したオブジェクト名がr
の場合、int
型の乱数を発生させたければn = r.Int()
とすれば、生成した乱数がn
に代入されます。
以下では、上記の中では少しだけ特殊なShuffleについて使い方を解説します。
Go言語のrand.Shuffleの使い方
Shuffleの定義は、Shuffle(n int, swap func(i, j int))
となっていて、シャッフル対象の配列等を渡す部分がありません。
この関数は以下のような形式で利用します(func()
の部分に、配列の入れ替えの処理を書く形です)。
package main
import (
"fmt"
"math/rand"
)
func main() {
var r *rand.Rand
r = rand.New(rand.NewSource(42))
const N = 10
a := make([]int, 0)
for i := 0; i < N; i++ {
a = append(a, i)
}
fmt.Println(a)
// [0 1 2 3 4 5 6 7 8 9]
r.Shuffle(N, func(i, j int) {
a[i], a[j] = a[j], a[i]
})
fmt.Println(a)
// [2 5 7 9 6 8 1 4 0 3]
}
少しわかりにくいかもしれませんが、この方法は応用範囲が広いです。
例えば5~9まで(5以上の値)をシャッフルしたくない場合は、以下のように記述すれば実現できます。
r.Shuffle(N, func(i, j int) {
if i < 5 && j < 5 {
a[i], a[j] = a[j], a[i]
}
})
fmt.Println(a)
// [2 0 4 3 1 5 6 7 8 9]
if
文により5未満しか置換されないため、結果として0~4まではシャッフルされ、5以上はシャッフルされません。
応用範囲は広いと思います
まとめ
Go言語をアップデートしたところ、VSCodeでrand.Seed()
にワーニングが発生したので調べてみました。参考になれば幸いです。