2013年4月2日火曜日

特定範囲の乱数生成について

特定範囲の乱数を得るのに剰余を使うと偏るそうです。
乱数生成のアンチパターン - Faith and Brave - C++で遊ぼう
今まで特に気にせず剰余を使ってました…乱数の扱いって難しいんですよね…
というわけで、どれくらい偏るのか検証してみました。

コード(ideone)
#include <random>
#include <iostream>

const int N=6;
const int X=1000000;
int main(int, char**)
{
    int n1[N]={},n2[N]={};
    ::std::random_device rd;
    unsigned int s = rd();
    ::std::mt19937 g1(s),g2(s);
    for( int i=0; i < X; ++i )
    {
        n1[ g1()%N ]++;
        n2[ ::std::uniform_int_distribution<unsigned int>(0, N-1)(g2) ]++;
    }
    for( int i=0; i < N; ++i )
    {
        ::std::cout << n1[i] << ", " << n2[i] << ::std::endl;
    }
    return 0;
}

結果

偏ってる?よくわかりませんね。
少しコードを変えてみます。

コード2(ideone)
#include <random>
#include <iostream>
#include <algorithm>

const int N=6;
const int X=10000;
const int C=10000;

void test(::std::vector<int>& c, unsigned int seed, unsigned int (*f)(::std::mt19937&))
{
    ::std::mt19937 g(seed);
    int n[N] ={};
    for( int i=0; i < X; ++i )
    {
        n[ f(g) ]++;
    }
    int r[N] = {};
    for( int i=0; i < N; ++i ) r[i] = i;
    ::std::sort(r, r+N, [&](int a, int b){ return n[a] > n[b]; });
    for( int i=0; i < N; ++i )
    {
        c[r[i]] += N/2 - i;
    }
}

int main(int, char**)
{
    ::std::random_device rd;
    ::std::vector<int> c1(N), c2(N);
    for( int j=0; j < C; ++j )
    {
        unsigned int s = rd();
        test(c1, s, [](::std::mt19937& g){ return g()%N; });
        test(c2, s, [](::std::mt19937& g){ return ::std::uniform_int_distribution<unsigned int>(0, N-1)(g); });
    }
    for( int i=0; i < N; ++i )
    {
        ::std::cout << i << ": " << c1[i] << ", " << c2[i] << ::std::endl;
    }
    return 0;
}

結果


確かに、偏っている…ような気がします。(ホントは全然わかってない。)
ただ、この偏りは逆に利用できるのかもしれません。

例えば、テトリスを考えてみてください。
縦棒のミノを大きい数値に割り当てれば、なんとなくやりごたえが上がるかもしれません。

もしくは、
レアカードを大きい数値に割り当てれば、自然と引きにくくなるかもしれません。

…かも

まとめ
乱数生成器を作るのは(検索すればすぐに情報が出てきますし)、そんなに難しいことではないと思います。
ただ、乱数生成器が作れたからといって、乱数を正しく扱えるわけではありません。

というわけで、C++11 の distribution はホントありがたいです。

0 件のコメント:

コメントを投稿