std::random 事始め
とある事情があり std::random を使うことに
libcのrandもあるんだけど、C++11には新しく乱数ライブラリが入りました
通常のrandは、線形合同法等の古いアルゴリズムを使っているため、乱数に偏りが出たり規則性が出る、循環が短い
等々問題点が多いので
ちょっと意識の高い人は メルセンヌツイスター法(MT法) を使ってたと思います
MT法は、乱数アルゴリズムの中でも、偏りが少なく計算も速く、非常に強力です
C++11では、MT法等が抽象化され標準で使えるようになりました
これは一様乱数を作成するものですが、他にも正規分布、ベルヌーイ分布、ポワソン分布等
色々な分布を作成する事が可能ですし
今まで (rand() % 6)+1 等と余りを使ったりして乱数を取得していたのが
乱数の範囲も指定できるようになります
(しかも 上記の場合においては rand()を使うより速くなる事が多い)
つまりは とっても強力
とりあえず、簡単に MT法で乱数を出すなら
#include
int main(){
std::mt19937 r(static_cast
(time(nullptr))); for (int i = 0; i < 10; ++i) {
std::cout << std::hex << r() << std::endl;
}
return(1);
}
std::mt19937 というのが乱数生成機で、乱数シードに timeを入れる
わかりやすいですね。
これで十分だと思うのですが、random_deviceやseed_seq を使う事で
より良い乱数を作れるらしい
(この辺の詳しい所は 数学得意な人に教えてもらいたい)
ので 定型文として
下記のようにすると より良い乱数が得られる
#include
int main(){
std::random_device rnd;
std::vector< std::uint_least32_t> v(10) ;
std::generate( v.begin(), v.end(), std::ref(rnd) ) ;
std::seed_seq q( v.begin(), v.end());
std::mt19937 mt{ q } ;
for (int i = 0; i < 10; ++i) {
std::cout << std::hex << mt() << std::endl;
}
return(1);
}
random_deviceというのが、素晴らしい乱数を作るらしいが、速度の関係か
これを毎回呼ぶのは好ましくないっぽく、シードとして使う
ちょっと 細かく追ってないので想定の範囲でゴメンナサイ。
今回は10個シードを持ちます
注目は std::ref() を使ってる所かな。
参照で渡さなければ
calling a private constructor of class
で コンパイルエラー。
2つ目の注目は mt19937の引数 seed_seqは rvalue不可能という事
// OK
std::seed_seq q( v.begin(), v.end());
std::mt19937 mt{ q } ;
// NG
std::mt19937 mt( std::seed_seq( v.begin(), v.end()) );
このへんは、理由を調べるのはコードを追っかけないとよくわからないので
定型文として 何も考えずに覚える事にします
これで 整数の乱数が作成出来たので さいころを実装するのに
(mt() % 6) +1
でも可能ですが、uniform_int_distribution を使うとより高速で便利に出来ます
さらに std::bind を使い、dice() という関数オブジェクトにしてしまいます
#include
int main(){
std::random_device rnd;
std::vector< std::uint_least32_t> v(10) ;
std::generate( v.begin(), v.end(), std::ref(rnd) ) ;
std::seed_seq q( v.begin(), v.end());
std::uniform_int_distribution
dist( 1, 6 ) ; auto dice=std::bind(std::uniform_int_distribution
(1,6), std::mt19937{q}); for (int i = 0; i < 10; ++i) {
std::cout << dice() << std::endl;
}
return(1);
}
んー いんじゃないですかー!