Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

はじめに

この記事は初心者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の乱数を得るためにやることが多すぎだ(´・_・`)

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away