わたくしのような凡人が決して立ち入ってはならない、確率を操る世界へ一歩だけ足を踏み入れてみたいと思います。
前もって固定の値を決めておかずに、実行させた時にランダムな値を得たい時ってあるよね~ (あるあるぅ~)
ハイ、そんな時にはこのrand()関数が使えるのさ。
関数仕様はこんな感じ
こう書けば1~10のうちいずれかを返してくれるぜぃ
条件式として0か1の真偽値を返すようにすれば50%の確率で条件内容が処理されるわけさ
応用として、1/5の確率ならこう書けばよかろう
3/5ならこうか
ではこの関数がどれだけ乱れるかを実験してみるぞなもし
■乱数実験1 確率と回数
・結果
このように、回数多くやった方がより均等にバラつかせられることが分かる。
つまりできるだけいいサンプル採るには多めにやれってことだね。
これはゲームとかシミュレーションに使えるぞな!
■乱数実験2 確率を割り当てる
例えば10%の確率でAになって、33%はBになって、残りはCになる・・・
みたいな感じで割り当てればよさそうなので、百分率という名のとおり、1~100が出るようにすれば割り当てがうまくできそうだ。
つまりザッとこんな感じで書けるだろう
ではこれが果たして想定どおりの確率になっているかを確かめる。
より精度が上がるよう1000000回試す
・結果
割とイケてる結果が出たんじゃないでしょうか?
何度やってもだいたいこれくらいの誤差で表示できました。
■乱数実験3 シミュレーション
最後にちょいとしたジャンケンのシミュレーションをば
ジャンケンは2人でやるものとして、人間クラスがまず必要。
あとはこの両者を登録して戦わせる為のバトルクラスを作ればうまくいきそうだね。
ってことで作って戦わせてみました。
・結果
この結果を見ますと、
つまり、ジャンケン強いと豪語する人に対しては何の感情も持たない乱数をうまく使えば対等な勝負ができそうだ。
ただ、ジャンケンは100万回も繰り返すことはないので、短期決戦、出たとこ勝負。
そう考えると、相手のクセを見抜いて勝つほうが確率が上がるだろう。
前もって固定の値を決めておかずに、実行させた時にランダムな値を得たい時ってあるよね~ (あるあるぅ~)
ハイ、そんな時にはこのrand()関数が使えるのさ。
関数仕様はこんな感じ
int rand(int $min, int $max);
こう書けば1~10のうちいずれかを返してくれるぜぃ
$num = rand(1, 10);
条件式として0か1の真偽値を返すようにすれば50%の確率で条件内容が処理されるわけさ
if (rand(0, 1)) { echo "夏だぜぃ!"; }
応用として、1/5の確率ならこう書けばよかろう
if (rand(1, 5) === 1) { }
3/5ならこうか
if (rand(1, 5) > 2) { }
ではこの関数がどれだけ乱れるかを実験してみるぞなもし
■乱数実験1 確率と回数
<?php // 100回 $result = simulate(100); print_s($result); // 10000回 $result = simulate(10000); print_s($result); // 1000000回 $result = simulate(1000000); print_s($result); function simulate($n) { $result = array_fill(1, 10, 0); //配列添え字1~10を0初期化 for ($i=0; $i<$n; $i++) { // $n回繰り返す //1~10のいずれが出たかをカウント $result[rand(1, 10)]++; } return $result; } function print_s($array) { printf("[%d回シミュレート]\n", array_sum($array)); foreach ($array as $i => $count) { printf("%2d-> %6d\n", $i, $count); } }
・結果
[100回シミュレート] 1-> 16 2-> 9 3-> 18 4-> 5 5-> 8 6-> 10 7-> 7 8-> 9 9-> 12 10-> 6 [10000回シミュレート] 1-> 966 2-> 985 3-> 977 4-> 1027 5-> 975 6-> 1003 7-> 982 8-> 1055 9-> 977 10-> 1053 [1000000回シミュレート] 1-> 100022 2-> 100006 3-> 100055 4-> 99995 5-> 100026 6-> 99988 7-> 100025 8-> 99956 9-> 100010 10-> 99917
このように、回数多くやった方がより均等にバラつかせられることが分かる。
つまりできるだけいいサンプル採るには多めにやれってことだね。
これはゲームとかシミュレーションに使えるぞな!
■乱数実験2 確率を割り当てる
例えば10%の確率でAになって、33%はBになって、残りはCになる・・・
みたいな感じで割り当てればよさそうなので、百分率という名のとおり、1~100が出るようにすれば割り当てがうまくできそうだ。
つまりザッとこんな感じで書けるだろう
$num = rand(1, 100); if ($num <= 10) { // 10%は echo 'A'; } else if ($num <= 10 + 33) { // 33%は echo 'B'; } else { // 残りは echo 'C'; }
ではこれが果たして想定どおりの確率になっているかを確かめる。
より精度が上がるよう1000000回試す
<?php define("SIMULATE_NUM", 1000000); for ($i=0; $i<SIMULATE_NUM; $i++) { $result[simulate()]++; } asort($result); foreach ($result as $Character => $count) { printf("%s-> %2.3f\n", $Character, $count / SIMULATE_NUM * 100); } // 設定確率でAかBかCを返す function simulate() { $num = rand(1, 100); if ($num <= 10) { $result = 'A'; } else if ($num <= 10 + 33) { $result = 'B'; } else { $result = 'C'; } return $result; }
・結果
A-> 9.998 B-> 33.001 C-> 57.000
割とイケてる結果が出たんじゃないでしょうか?
何度やってもだいたいこれくらいの誤差で表示できました。
■乱数実験3 シミュレーション
最後にちょいとしたジャンケンのシミュレーションをば
ジャンケンは2人でやるものとして、人間クラスがまず必要。
あとはこの両者を登録して戦わせる為のバトルクラスを作ればうまくいきそうだね。
ってことで作って戦わせてみました。
<?php define("GOO" , "goo" ); define("CHOKI", "choki"); define("PAA" , "paa" ); class Person { private $_goo = 33; private $_choki = 33; private $_paa = 33; public function setGoo($per) { $this->_goo = $per; } public function setChoki($per) { $this->_choki = $per; } public function setPaa($per) { $this->_paa = $per; } //設定の確率でジャンケンを繰り出す public function janken() { $result = rand(1, 100); if ($result <= $this->_goo) { return GOO; } else if ($result <= $this->_goo + $this->_choki) { return CHOKI; } else { return PAA; } } } class Battle { private $_person1; private $_person2; public function setPerson1($obj) { $this->_person1 = $obj; } public function setPerson2($obj) { $this->_person2 = $obj; } private function _judge($person1, $person2) { if ($person1 === GOO && $person2 === CHOKI || $person1 === CHOKI && $person2 === PAA || $person1 === PAA && $person2 === GOO) { // person1 win return '1win'; } else if ($person1 === $person2) { // draw return 'draw'; } else { return '2win'; } } public function go() { return $this->_judge($this->_person1->janken(),$this->_person2->janken()); } } // 人間オブジェクト1と2作成 $person1 = new Person(); $person2 = new Person(); // 第1バトルのタイトル表示 echo "Battle1): 1(33,33,34), 2(33,33,34)\n"; // 属性設定 $person1->setGoo(33); $person1->setChoki(33); $person1->setPaa(34); $person2->setGoo(33); $person2->setChoki(33); $person2->setPaa(34); // バトルオブジェクト作成 $battle = new Battle(); $battle->setPerson1($person1); $battle->setPerson2($person2); // バトル for ($i=0; $i<1000000; $i++) { $result[$battle->go()]++; } // 結果表示 print_r($result); // 第2バトル echo "Battle2): 1(33,33,34), 2(10,10,80)\n"; $person2->setGoo(10); $person2->setChoki(10); $person2->setPaa(80); $result = array(); for ($i=0; $i<1000000; $i++) { $result[$battle->go()]++; } print_r($result); // 第3バトル echo "Battle3): 1(33,33,34), 2(0,0,100)\n"; $person2->setGoo(0); $person2->setChoki(0); $person2->setPaa(100); $result = array(); for ($i=0; $i<1000000; $i++) { $result[$battle->go()]++; } print_r($result); // 第4バトル echo "Battle4): 1(50,50,0), 2(0,50,50)\n"; $person1->setGoo(50); $person1->setChoki(50); $person1->setPaa(0); $person2->setGoo(0); $person2->setChoki(50); $person2->setPaa(50); $result = array(); for ($i=0; $i<1000000; $i++) { $result[$battle->go()]++; } print_r($result);
・結果
Battle1): 1(33,33,34), 2(33,33,34) Array ( [draw] => 333298 [2win] => 333196 [1win] => 333506 ) Battle2): 1(33,33,34), 2(10,10,80) Array ( [draw] => 338097 [1win] => 330884 [2win] => 331019 ) Battle3): 1(33,33,34), 2(0,0,100) Array ( [1win] => 329962 [draw] => 340029 [2win] => 330009 ) Battle4): 1(50,50,0), 2(0,50,50) Array ( [2win] => 249812 [1win] => 500372 [draw] => 249816 )
この結果を見ますと、
- グー・チョキ・パー平均的に出す人同士はやはり勝ち負けあいこすべて同程度
- 片方だけが割合変えると、あいこは多くなるが勝ち負けは同程度
- グー・チョキ・パーのうち、どれか1つを出さない者同士でジャンケンすると、かみ合わせいい方が倍勝てる
つまり、ジャンケン強いと豪語する人に対しては何の感情も持たない乱数をうまく使えば対等な勝負ができそうだ。
ただ、ジャンケンは100万回も繰り返すことはないので、短期決戦、出たとこ勝負。
そう考えると、相手のクセを見抜いて勝つほうが確率が上がるだろう。