Randomize a PHP array with a seed?

2020-01-28 03:57发布

问题:

I'm looking for a function that I can pass an array and a seed to in PHP and get back a "randomized" array. If I passed the same array and same seed again, I would get the same output.

I've tried this code

//sample array
$test = array(1,2,3,4,5,6);
//show the array
print_r($test);

//seed the random number generator
mt_srand('123');
//generate a random number based on that
echo mt_rand();
echo "\n";

//shuffle the array
shuffle($test);

//show the results
print_r($test);

But it does not seem to work. Any thoughts on the best way to do this?

This question dances around the issue but it's old and nobody has provided an actual answer on how to do it: Can i randomize an array by providing a seed and get the same order? - "Yes" - but how?

Update

The answers so far work with PHP 5.1 and 5.3, but not 5.2. Just so happens the machine I want to run this on is using 5.2.

Can anyone give an example without using mt_rand? It is "broken" in php 5.2 because it will not give the same sequence of random numbers based off the same seed. See the php mt_rand page and the bug tracker to learn about this issue.

回答1:

You can use array_multisort to order the array values by a second array of mt_rand values:

$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);

Here $order is an array of mt_rand values of the same length as $arr. array_multisort sorts the values of $order and orders the elements of $arr according to the order of the values of $order.



回答2:

Sorry, but accordingly to the documentation the shuffle function is seeded automatically.

Normally, you shouldn't try to come up with your own algorithms to randomize things since they are very likely to be biased. The Fisher-Yates algorithm is known to be both efficient and unbiased though:

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;
    }
}

Example (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
)


回答3:

The problem you have is that PHP comes with two random number generators built in.

The shuffle() command does not use the mt_rand() random number generator; it uses the older rand() random number generator.

Therefore, if you want shuffle() to use a seeded number sequence, you need to seed the older randomiser, using srand() rather than mt_srand().

In most other cases, you should use mt_rand() rather than rand(), since it is a better random number generator.



回答4:

The main question involves two parts. One is about how to shuffle. The other is about how to add randomness to it.

A simple solution

This is probably the simplest answer to the main question. It is sufficient for most cases in PHP scripting. But not all (see below).

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;
}

The above method will do, even though it doesn't produce true random shuffles for all possible seed-array combinations. However, if you really want it to be balanced and all, I guess PHP shuldn't be your choice.

A more useful solution for advanced programmers

As stated by André Laszlo, randomization is a tricky business. It is usually best to let a dedicated object handle it. My point is, that you shouldn't need to bother with the randomness when you write the shuffle function. Depending on what degree of ramdomness you would like in your shuffle, you may have a number of PseudoRandom objects to choose from. Thus the above could look like this:

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;
}

Now, this solution is the one I would vote for. It separates shuffle codes from randomization codes. Depending on what kind of random you need you can subclass PseudoRandom, add the needed methods and your preferred formulas. And, as the same shuffle function may be used with many random algorithms, one random algorithm may be used in different places.



回答5:

A variant that also works with PHP version 7.2, because the php function create_function is deprecated in the newest php version.

mt_srand($seed);

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

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


回答6:

In recent PHP versions, seeding the PHP builtin rand() and mt_rand() functions will not give you the same results everytime. The reason for this is not clear to me (why would you want to seed the function anyway if the result is different every time.) Anyway, it seems like the only solution is to 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;
    }
}

Usage:

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

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

The code above will output the folowing sequence every time you run it:

76
86
14
79
73
2
87
43
62
7

Just change the seed to get a completely different "random" sequence



回答7:

I guess this will do the job :

    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;

}

In order to not limit the resulting array, set $number_of_items_wanted to -1 In order to not use a seed, set $seed to FALSE



回答8:

Seeded shuffle while maintaining the key index:

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:

This seems the easiest for me...

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