基本
「mt_rand
のシードを入力文字列のハッシュ値で固定すればいいじゃん!」 と真っ先に思いつくんですが,mt_srand
の引数には整数しか渡せないので,md5
やsha1
などはそのままでは適用できません.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さんの今日の運勢は末吉です!ラッキーカラーは緑です!
*/
なお,シード値が配列の複数要素になる場合にはserialize
やjson_encode
したものを$seed
として渡せばいいでしょう.