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()にワーニングが発生したので調べてみました。参考になれば幸いです。

