麻雀の山配りのときに出てくる問題点を提示しようしている。その前に今回は、擬似乱数の説明。
1.擬似乱数
Cのrand()やC#(.NET)のRandomは「擬似乱数」。定義はwikipediaなどで。
2.毎回同じ
このプログラムを動かしてみる
for(;;){
Random r = new Random(0); //ここでは明確にシードを0と指定
for(Int32 i=0;i<30;i++){
Console.Write("
}
Console.Write("
}
結果はこれ。
72 81 76 55 20 55 90 44 97 27 29 46 63 46 98 03 86 99 67 31
72 81 76 55 20 55 90 44 97 27 29 46 63 46 98 03 86 99 67 31
72 81 76 55 20 55 90 44 97 27 29 46 63 46 98 03 86 99 67 31
・・・
このように、シードが同じならば返り値の振る舞いがいつもいっしょ。(実は、この再現性があるという性質はテストやデバッグには重宝する。)
3.内部状態とループ
擬似乱数器の内部状態のとりうる状態が有限ならば、いつかはループする。
以下はそれを自前のTinyRandを作って確認してみた。「簡単のため10回続けて同じならば、もう同じと判定している」
// 自前の擬似乱数発生 wikipediaの混合合同法をほぼマルパクリ。
class TinyRand
{
Int64 m_a;
Int64 m_p;
Int64 m_q;
// 14992×673+944 = 10090560 → 09056
public TinyRand()
{
m_a=14992;
m_p=673;
m_q=944;
}
public Int32 Next()
{
m_a = ((m_a * m_p + m_q) /10)%1000000000
return ((Int32)m_a);
}
}
// 比較のため10回分は記憶しておく
class TameRand
{
const Int32 ten = 10;
TinyRand m_random;
Int32 m_cursor;
Int32[] m_tame;
public TameRand()
{
m_random = new TinyRand();
m_tame = new Int32[ten];
for (Int32 i = 0; i < ten; i++)
{
m_tame[i] = m_random.Next()
}
m_cursor = 0;
}
public Int32 Next()
{
Int32 ret=m_tame[m_cu
m_tame[m_cursor
m_cursor=(m_cur
return (ret);
}
public bool Equals(TameRand
{
for (Int32 i = 0; i < ten; i++)
{
if (tr.m_tame[(tr.
{
return (false);
}
}
return (true);
}
}
static void Main(string[] args)
{
// 「ウサギとカメ方式」でループチェック
{
TameRand ur = new TameRand();
TameRand kr = new TameRand();
for (Int32 i=1; ;i++)
{
ur.Next(); // うさぎ君は2倍すすませて
if (ur.Next() == kr.Next())
{
Console.Write("
if (ur.Equals(kr))
{
Console.Write("
for (; ; ) { }
}
}
}
}
}
結果は
ループしたかなぁ? i=38904
ループしたようだ。 i=38904
38904番目からの10個とその倍の77808番目からの10個の数字がまるっきり同じ。実際には10個だけでなく、そこからずっといっしょ。
実は、同じ方法でC#のRandomのループ状態を確認しようとしたが、数分かけても捕まえられず。Cのrand()ならもっと短い周期でループしていたように記憶しているが。