【问题标题】:Randomize a PHP array with a seed?用种子随机化 PHP 数组?
【发布时间】:2011-09-27 07:40:13
【问题描述】:

我正在寻找一个函数,我可以在 PHP 中传递一个数组和一个种子并取回一个“随机”数组。如果我再次传递相同的数组和相同的种子,我将得到相同的输出。

我试过这段代码

//样本数组 $test = 数组(1,2,3,4,5,6); //显示数组 print_r($test); //种子随机数生成器 mt_srand('123'); //根据它生成一个随机数 回声 mt_rand(); 回声“\n”; //打乱数组 洗牌($测试); //显示结果 print_r($test);

但它似乎不起作用。对最好的方法有什么想法吗?

这个问题绕着这个问题跳舞,但它已经过时了,没有人提供关于如何做到这一点的实际答案:Can i randomize an array by providing a seed and get the same order? - “是” - 但是如何?

更新

到目前为止,答案适用于 PHP 5.1 和 5.3,但不适用于 5.2。恰好我要运行它的机器使用的是 5.2。

谁能在不使用 mt_rand 的情况下举个例子?它在 php 5.2 中被“破坏”了,因为它不会基于相同的种子给出相同的随机数序列。请参阅php mt_rand pagebug tracker 了解此问题。

【问题讨论】:

  • 你注意到这里的更新日志了吗:php.net/manual/en/function.shuffle.php?它说,从 php 4.2.0 开始,您不需要手动为随机数生成器播种。
  • 让我确定我明白你想要什么。您想要一个“随机”数组,但又希望它能够被复制?
  • 他需要同样的订单,所以他必须这样做。
  • 您可能需要创建自己的随机数生成器和 Array Shuffle。查看此链接了解实现细节:shamimhafiz.wordpress.com
  • 下面有一些很好的答案。我不知道如何选择最好的。

标签: php arrays random seed


【解决方案1】:

对不起,但据the documentation shuffle 函数是自动播种的。

通常,您不应该尝试提出自己的算法来随机化事物,因为它们很可能存在偏见。 Fisher-Yates algorithm 以高效和公正而著称:

function fisherYatesShuffle(&$items, $seed)
{
    @mt_srand($seed);
    for ($i = count($items) - 1; $i > 0; $i--)
    {
        $j = @mt_rand(0, $i);
        $tmp = $items[$i];
        $items[$i] = $items[$j];
        $items[$j] = $tmp;
    }
}

示例(PHP 5.5.9):

php > $original = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
php > $shuffled = (array)$original;
php > fisherYatesShuffle($shuffled, 0);
php > print_r($shuffled);
Array
(
    [0] => 6
    [1] => 0
    [2] => 7
    [3] => 2
    [4] => 9
    [5] => 3
    [6] => 1
    [7] => 8
    [8] => 5
    [9] => 4
)
php > $shuffled = (array)$original;
php > fisherYatesShuffle($shuffled, 0);
php > print_r($shuffled);
Array
(
    [0] => 6
    [1] => 0
    [2] => 7
    [3] => 2
    [4] => 9
    [5] => 3
    [6] => 1
    [7] => 8
    [8] => 5
    [9] => 4
)

【讨论】:

  • 这是一个很好的解决方案,但是从 PHP 5.3.1 开始,mt_srand “相同的种子不再产生与以前版本相同的值序列。” - 所以这不再是可预测的洗牌。我找到并使用了 Mersenne_Twister 类而不是 mt_srand 和 mt_rand,这种方法给了我一个可预测/可重复的随机播放。
  • @drchuck。这将是可预测的,只是与 5.2.1 之前的 PHP 版本产生的顺序不同。仍然值得注意。
【解决方案2】:

您可以使用array_multisort 按第二个mt_rand 值数组对数组值进行排序:

$arr = array(1,2,3,4,5,6);

mt_srand('123');
$order = array_map(create_function('$val', 'return mt_rand();'), range(1, count($arr)));
array_multisort($order, $arr);

var_dump($arr);

这里的$order 是一个由mt_rand 值组成的数组,其长度与$arr 相同。 array_multisort$order的值进行排序,$arr的元素按照$order的值排序。

【讨论】:

  • 我认为你在 array_multisort($order, $arr); 中混合了 $order 和 $arr;
  • 其实这个答案可能不适用于所有版本的php。我刚刚注意到运行 5.2.17 的服务器将为 $order 变量创建一个随机数字序列。在这里也找到了一个注释:从 5.2.1 开始,PHP 中的 Mersenne Twister 实现现在使用 Richard Wagner 的新播种算法。相同的种子不再产生与以前版本相同的值序列。这种行为预计不会再次改变,但仍然认为依赖它是不安全的。 - php.net/manual/en/function.mt-srand.php
  • @cwd:那么你可能不得不坚持旧的randsrand
  • create_function 自 PHP 7.2.0 起已弃用
  • 请使用function ($val) { return mt_rand(); } 而不是create_function('$val', 'return mt_rand();')
【解决方案3】:

您遇到的问题是 PHP 内置了两个随机数生成器。

shuffle() 命令不使用mt_rand() 随机数生成器;它使用较旧的rand() 随机数生成器。

因此,如果您希望 shuffle() 使用种子编号序列,则需要使用 srand() 而不是 mt_srand() 为较旧的随机发生器设置种子。

在大多数其他情况下,您应该使用mt_rand() 而不是rand(),因为它是一个更好的随机数生成器。

【讨论】:

  • 嗯,使用 srand(123) 然后 rand() 似乎并不总是在 php 5.2.17 上产生相同的输出......
【解决方案4】:

主要问题涉及两个部分。一是关于如何洗牌。另一个是关于如何给它添加随机性。

一个简单的解决方案

这可能是对主要问题的最简单答案。对于 PHP 脚本中的大多数情况,这已经足够了。但不是全部(见下文)。

function /*array*/ seedShuffle(/*one dimentional array*/ $array, /*integer*/ $seed) {
    $tmp = array();
    for ($rest = $count = count($array);$count>0;$count--) {
        $seed %= $count;
        $t = array_splice($array,$seed,1);
        $tmp[] = $t[0];
        $seed = $seed*$seed + $rest;
    }
    return $tmp;
}

上述方法可以,即使它不会为所有可能的种子数组组合产生真正的随机洗牌。但是,如果你真的希望它是平衡的,我想 PHP 不应该是你的选择。

对高级程序员更有用的解决方案

正如 André Laszlo 所说,随机化是一项棘手的工作。通常最好让专用对象处理它。我的观点是,当您编写 shuffle 函数时,您不必担心随机性。根据您希望随机播放的随机程度,您可能有许多 PseudoRandom 对象可供选择。因此,上面的内容可能如下所示:

abstract class PseudoRandom {
    protected abstract function /*integer*/ nextInt();
    public function /*integer*/ randInt(/*integer*/ $limit) {
        return $this->nextInt()%$limit;
    }
}

function /*array*/ seedShuffle($array, /*PseudoRandom Object*/ $rnd) {
    $tmp = array();
    $count = count($array);
    while($count>0) {
        $t = array_splice($array,$rnd->randInt($count--),1);
        $tmp[] = $t[0];
    }
    return $tmp;
}

现在,我会投票支持这个解决方案。它将随机码与随机码分开。根据您需要的随机类型,您可以将 PseudoRandom 子类化,添加所需的方法和您喜欢的公式。 并且,由于同一个shuffle函数可以和很多随机算法一起使用,所以一个随机算法可以用在不同的地方。

【讨论】:

  • 谢谢!简单的一个为我做了诀窍,其中选择的答案根据数组大小而崩溃。非常感激! :-)
【解决方案5】:

在最近的 PHP 版本中,植入 PHP 内置 rand()mt_rand() 函数不会每次都给您相同的结果。我不清楚其原因(如果每次结果都不同,为什么还要为函数播种。)无论如何,似乎唯一的解决方案是write your own random function

class Random {

    // random seed
    private static $RSeed = 0;

    // set seed
    public static function seed($s = 0) {
        self::$RSeed = abs(intval($s)) % 9999999 + 1;
        self::num();
    }

    // generate random number
    public static function num($min = 0, $max = 9999999) {
        if (self::$RSeed == 0) self::seed(mt_rand());
        self::$RSeed = (self::$RSeed * 125) % 2796203;
        return self::$RSeed % ($max - $min + 1) + $min;
    }
}

用法:

// set seed
Random::seed(42);

// echo 10 numbers between 1 and 100
for ($i = 0; $i < 10; $i++) {
    echo Random::num(1, 100) . '<br />';
}

上面的代码每次运行都会输出以下序列:

76
86
14
79
73
2
87
43
62
7

只需更改种子以获得完全不同的“随机”序列

【讨论】:

  • 这个算法,又名算法 266 (MC Pike & ID Hill, 1965) 真的很老了 - 它发表于 1965 年。从某种意义上说它可能不够“随机” 相对容易让某人根据 PRNG 的先前输出预测下一个生成的数字。主要是因为它的周期很短——它在不到 300 万件商品之后就会回绕。也许PHP implementation of the Mersenne Twister 是更好的选择?
  • 另外,如果种子功能不再起作用,您应该创建一个脚本来重现它和post a bug report。 PHP 中 PRNG 的问题在于它们具有全局状态,如果您的代码的另一部分,甚至是库使用 srandmt_srand,您就会遇到问题。你确定这不是发生在你身上的事吗?例如,在 Java 或 Python 中,您可以实例化 PRNG 以对代码的不同部分使用不同的种子,这当然要好得多。
【解决方案6】:

也适用于 PHP 7.2 版本的变体,因为 php 函数 create_function 在最新的 php 版本中已被弃用。

mt_srand($seed);

$getMTRand = function () {
    return mt_rand();
};

$order = array_map($getMTRand, range(1, count($array)));
array_multisort($order, $array);
return $array;

【讨论】:

    【解决方案7】:

    我想这可以完成这项工作:

        function choose_X_random_items($original_array , $number_of_items_wanted = -1 , $seed = FALSE ){
    
    //save the keys
    foreach ($original_array as $key => $value) {
    
        $original_array[$key]['key_memory'] = $key;
    
    }
    
    $original_array = array_values($original_array);
    $results = array();
    if($seed !== FALSE){srand($seed);}
    $main_random = rand();
    $random = substr($main_random,0,( $number_of_items_wanted == -1 ? count($original_array) : min($number_of_items_wanted,count($original_array)) ));
    $random = str_split($random);
    
    foreach ($random AS $id => $value){
    
    
        $pick = ($value*$main_random) % count($original_array);
        $smaller_array[] = $original_array[$pick];
        unset($original_array[$pick]);
            $original_array = array_values($original_array);
    
    }
    
    
    //retrieve the keys
    foreach ($smaller_array as $key => $value) {
    
        $smaller_array[$value['key_memory']] = $value;
        unset($smaller_array[$value['key_memory']]['key_memory']);
        unset($smaller_array[$key]);
    
    }
    
    return $smaller_array;
    
    }
    

    为了不限制结果数组,将 $number_of_items_wanted 设置为 -1 为了不使用种子,请将 $seed 设置为 FALSE

    【讨论】:

      【解决方案8】:

      在保持键索引的同时进行种子洗牌:

      function seeded_shuffle(array &$items, $seed = false) {
          
          mt_srand($seed ? $seed : time());
          
          $keys = array_keys($items);
          $items = array_values($items);
          
          for ($i = count($items) - 1; $i > 0; $i--) {
              $j = mt_rand(0, $i);
              list($items[$i], $items[$j]) = array($items[$j], $items[$i]);
              list($keys[$i], $keys[$j]) = array($keys[$j], $keys[$i]);
          }
          
          $items = array_combine($keys, $items);
      }
      

      【讨论】:

        【解决方案9】:

        一个简单的解决方案:

        $pool = [1, 2, 3, 4, 5, 6];
        $seed = 'foo';
        
        $randomIndex = crc32($seed) % count($pool);
        $randomElement = $pool[$randomIndex];
        

        它可能不像 Fisher Yates shuffle 那样随机,但我发现它给了我足够的熵来满足我的需要。

        【讨论】:

          【解决方案10】:

          根据@Gumbo、@Spudley、@AndreyP 的回答,它的工作原理如下:

          $arr = array(1,2,3,4,5,6);
          
          srand(123); //srand(124);
          $order = array_map(function($val) {return rand();}, range(1, count($arr)));
          array_multisort($order, $arr);
          
          var_dump($arr);
          

          【讨论】:

            【解决方案11】:

            这对我来说似乎是最简单的......

            srand(123);
            usort($array,function($a,$b){return rand(-1,1);});
            

            【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-05-30
            • 1970-01-01
            • 2022-08-03
            • 1970-01-01
            • 1970-01-01
            • 2016-10-07
            相关资源
            最近更新 更多