C++の乱数ライブラリが使いにくい話

  • 7
    いいね
  • 3
    コメント

はじめに

この記事は初心者C++er Advent Calendar 2016の14日目の記事です。

C++のライブラリってすごく機能がいっぱいあるように思うんですけど、なんか汎用的すぎたり、痒いところに手が届かなかったりするんですよね。
今回はそんな話をしたいと思います。

真の乱数

<random>ヘッダの話をします。
乱数ってよく使いますよね。

でもパソくんはサイコロを振る事ができません。計算をすることしかできないのです。
まあ、そこで現在の時刻とかCPUの温度とかを使ってランダムっぽい数値を引っ張ってきます。

それをやってくれるのは

std::random_deviceというやつである。

これはクラスなので

std::random_device rd{};

std::cout << rd() << std::endl;

のようにして、いちいちオブジェクトを作ったりして使う。

random_deviceクラスのoperator()を呼び出すと乱数を得ることができる。

備考(C++がチョットできる人向け)

このstd::random_deviceというクラスはコピーもムーブもできない。
したがってstd::vector<std::uint32_t>をgenerateを使って乱数で埋めたい場合次のようになる。

std::random_device rd{};

std::vector<std::uint32_t> vec(10);

std::generate( vec.begin(), vec.end(), std::ref(rd) );

std::ref(rd)のようになります。
std::ref<functional>にあったと思います。

疑似乱数

いちいち真の乱数を引っ張ってくるのはコストであるため、最初乱数(初期シード)から乱数っぽい値を計算してくれるアルゴリズム的なものがあります。
それを擬似乱数と呼びます。

これらには種類がいろいろとありまして、

  • minstd_rand0
  • minstd_rand
  • mt19937
  • mt19937_64
  • ranlux24_base
  • ranlux48_base
  • ranlux24
  • ranlux48
  • knuth_b
  • default_random_engine

と、これだけあります。

std::mt19937を使っとけばだいたい問題がナイと思います(適当

seed と seed_seq

擬似乱数を使う場合、

std::mt19937 mt;

std::cout << mt() << std::endl;

のようにすれば擬似乱数が得られる。
しかしこの場合はプログラムの実行をするたびに同じ値が表示される。

擬似乱数には初期値として初期シードと呼ばれる乱数を与えなければならない。

std::random_device rd;

std::mt19937 mt(rd());

std::cout << mt() << std::endl;

が正しい。

さて、C++にはstd::seed_seqなるクラスが用意されていてこれで擬似乱数生成器クラスを初期化できる。

std::seed_seqはイテレータのペアをとる。

std::random_device rd{};

std::vector<std::uint32_t> vec(10);

std::generate( vec.begin(), vec.end(), std::ref(rd) );

std::mt19937 mt(std::seed_seq(vec.begin(), vec.end()));

とやってやるのだ。

Distribution

最後に、ディストリビューションについてでさ。

ランダムな整数って使いにくい、1~6とかが使いやすい。
そう思うのは当たり前である。

それを実現するのがdistribution達である。

一様分布乱数

整数のやつ。
std::uniform_int_distribution<IntType>

IntTypeには好きな整数型を指定する。

std::mt19937 mt{ std::random_device{}() };

std::uniform_int_distribution<int> dist(1, 6);

for ( int i = 0 ; i != 10 ; ++i )
  std::cout << dist(mt) << std::endl;

使い方はご覧の通りである。

dist(1, 6)とすれば1~6になる。
呼び出すときには、擬似乱数生成エンジンを渡す。

浮動小数点のやつ。
std::uniform_real_distribution<RealType>
もあるよ。
使い方は一緒。

分布乱数は他にもたくさんあります。
ベルヌーイ分布・正規分布・ポワソン分布・標本分布などがあります。

乱数まとめ

  • 擬似乱数生成エンジンを使う(mt19937でおk)

  • エンジンを真の乱数で初期化する(std::random_deviceを使おう)

  • 分布乱数を使うときは分布乱数クラスを使う。エンジンを渡して呼び出す。

面倒くさい、1~6の乱数を得るためにやることが多すぎだ(´・_・`)

サクッと書きたいんだよって人、朗報ですよ。
C++17から気の利いた乱数生成してくれる関数的な何かが入るらしいです。