PHP
Twitter

PHPでTwitterの診断メーカーで使われているような「入力値に依存する乱数」を生成する方法

More than 3 years have passed since last update.


基本

mt_randのシードを入力文字列のハッシュ値で固定すればいいじゃん!」 と真っ先に思いつくんですが,mt_srand の引数には整数しか渡せないので,md5sha1などはそのままでは適用できません.crc32関数がちょうど整数型でハッシュ値を返してくれる関数なので,今回はこれを利用することにします.

※ 32bit環境と64bit環境で得られる結果が異なるので注意してください


PHP5.4以降

/**

* @param string $seed シード値
* @param int $min 最小値
* @param int $max 最大値
* @param bool $daily 日付毎にランダマイズするか
* @return int 範囲内の乱数
*/

function random_int_by_seed($seed, $min, $max, $daily = false) {
$seed = (string)filter_var($seed);
$min = (int)$min;
$max = (int)$max;
if ($max < $min) {
throw new \InvalidArgumentException('Invalid range');
};
if ($daily) {
$seed .= (new \DateTime('today Asia/Tokyo'))->format('Y/m/d');
}
mt_srand(crc32($seed));
$r = mt_rand($min, $max);
mt_srand();
return $r;
}


バラつき度の確認

$counts = array_fill(0, 5, 0);

for ($i = 0; $i < 10000; ++$i) {
++$counts[random_int_by_seed(openssl_random_pseudo_bytes(100), 0, 4)];
}
var_dump($counts);

/*
array(5) {
[0]=>
int(2002)
[1]=>
int(2037)
[2]=>
int(1968)
[3]=>
int(2023)
[4]=>
int(1970)
}
*/



応用

実際は,乱数に応じて複数の選択肢を選んでいくことになるはずなので,これに適応できるようにちょっと書き加えます.


PHP5.4以降

/**

* @param string $seed シード値
* @param array $table 「選択肢の配列」の配列
* @param bool $daily 日付毎にランダマイズするか
* @return array 「選ばれた選択肢」の配列
*/

function random_items_by_seed($seed, array $table, $daily = false) {
$seed = (string)filter_var($seed);
if ($daily) {
$seed .= (new \DateTime('today Asia/Tokyo'))->format('Y/m/d');
}
mt_srand(crc32($seed));
$r = [];
foreach ($table as $i => $row) {
$row = (array)$row;
$r[$i] = $row ? $row[mt_rand(0, count($row) - 1)] : null;
};
mt_srand();
return $r;
}


使用例

$name = 'mpyw';

$r = random_items_by_seed($name, [
['大吉', '吉', '中吉', '小吉', '末吉', '凶', '大凶'],
['赤', '青', '緑', 'ピンク'],
], true);
vprintf("{$name}さんの今日の運勢は%sです!ラッキーカラーは%sです!\n", $r);

/*
mpywさんの今日の運勢は末吉です!ラッキーカラーは緑です!
*/


なお,シード値が配列の複数要素になる場合にはserializejson_encodeしたものを$seedとして渡せばいいでしょう.