15
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-03-07

基本

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として渡せばいいでしょう.

15
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?