36

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.

Community
  • 1
  • 1
cwd
  • 53,018
  • 53
  • 161
  • 198
  • Have you noticed the changelog here: http://php.net/manual/en/function.shuffle.php ? It says that since php 4.2.0 you won't need to seed the random number generator manually. – Philipp Jul 02 '11 at 15:21
  • Let me make sure I understand what you want. You want a 'random' array, but you want it to be able to be duplicated? – Drazisil Jul 02 '11 at 15:25
  • 1
    He needs the same order so he *has to* do it. – Karoly Horvath Jul 02 '11 at 15:27
  • 1
    You may need to create your own random number generator and Array Shuffling. Look at this link for implementation detail: http://shamimhafiz.wordpress.com/ – Shamim Hafiz - MSFT Jul 02 '11 at 15:43
  • There are some great answers below. I'm not sure how to choose the best one. – cwd Jul 03 '11 at 01:45
  • If [this request](https://bugs.php.net/bug.php?id=66718) is ever accepted, your problem will be solved. It [seems to have been ignored](https://wiki.php.net/rfc/use-php_mt_rand) though. – André Laszlo Apr 11 '15 at 15:35

12 Answers12

51

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
)
Temüjin
  • 15,371
  • 8
  • 35
  • 57
André Laszlo
  • 15,169
  • 3
  • 63
  • 81
  • 2
    This is a great solution, but since PHP 5.3.1 the mt_srand "Identical seeds no longer produce the same sequence of values they did in previous versions." - so this is no longer a predictable shuffle. I found and used a Mersenne_Twister class instead of the mt_srand and mt_rand and this approach gave me a predictable/repeatable shuffle. – drchuck Sep 30 '15 at 16:00
  • 4
    @drchuck. It will be predictable, just not the same sequence as produced by PHP versions prior to 5.2.1. Still worth noting. – André Laszlo Oct 01 '15 at 11:52
22

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.

Gumbo
  • 643,351
  • 109
  • 780
  • 844
  • I think you have $order and $arr mixed in array_multisort($order, $arr); – Shard Jul 04 '11 at 23:49
  • No (see the first example on the [`array_multisort` reference page](http://php.net/array_multisort)). – Gumbo Jul 05 '11 at 05:36
  • 2
    Actually maybe this answer won't work for all versions of php. I just noticed that a server running 5.2.17 will create a random sequence of numbers for the $order variable. Found a note here as well: Since 5.2.1 The Mersenne Twister implementation in PHP now uses a new seeding algorithm by Richard Wagner. Identical seeds no longer produce the same sequence of values they did in previous versions. This behavior is not expected to change again, but it is considered unsafe to rely upon it nonetheless. - http://php.net/manual/en/function.mt-srand.php – cwd Jul 08 '11 at 04:08
  • 2
    @cwd: Then you will probably have to stick to the old `rand` and `srand`. – Gumbo Jul 11 '11 at 10:38
  • 3
    `create_function` has been DEPRECATED as of PHP 7.2.0 – Hossein Jan 02 '19 at 16:17
  • 5
    please use `function ($val) { return mt_rand(); }` instead of `create_function('$val', 'return mt_rand();')` – AndreyP Sep 04 '20 at 14:41
5

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.

Spudley
  • 166,037
  • 39
  • 233
  • 307
  • 2
    hmm, using srand(123) and then rand() does not always seem to produce the same output on php 5.2.17... – cwd Jul 08 '11 at 04:14
2

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.

user1904991
  • 140
  • 5
  • Thanks! The simple one did the trick for me where the chosen answer crashed depending on the array size. Much appreciated! :-) – Stan Smulders Jan 02 '18 at 14:42
1

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

Jules Colle
  • 11,227
  • 8
  • 60
  • 67
  • This algorithm, a.k.a. Algorithm 266 (M.C. Pike & I.D. Hill, 1965) is really old - It was published in 1965. It might not be "random enough" in the sense that it _could_ be relatively easy for someone to predict the next generated number based on previous output of the PRNG. Mainly because it has a short period - it wraps around after less than 3 million items. Maybe a [PHP implementation of the Mersenne Twister](https://github.com/ruafozy/php-mersenne-twister) is a better alternative? – André Laszlo Apr 11 '15 at 15:22
  • Also, if the seed functions are no longer working you should create a script to reproduce it and [post a bug report](https://bugs.php.net). The problem with the PRNGs in PHP is that they have global state, if another part of your code, or even a library uses `srand` or `mt_srand` you will have problems. Are you sure this is not what happens to you? In Java or Python, for example, you can instantiate the PRNG to use different seeds for different parts of your code, which is much better of course. – André Laszlo Apr 11 '15 at 15:29
1

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;
seinol
  • 71
  • 10
0

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

GIlles
  • 1
  • 1
0

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);
}
Community
  • 1
  • 1
Martin Zvarík
  • 2,120
  • 22
  • 27
0

A simple solution:

$pool = [1, 2, 3, 4, 5, 6];
$seed = 'foo';

$randomIndex = crc32($seed) % count($pool);
$randomElement = $pool[$randomIndex];

It might not be quite as random as the Fisher Yates shuffle, but I found it gave me more than enough entropy for what I needed it for.

Jonathan
  • 13,947
  • 17
  • 94
  • 123
0

Based on @Gumbo, @Spudley, @AndreyP answers, it works like that:

$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);
catalinp
  • 131
  • 2
  • 5
0

Home Made function, using crc32(note:also returns negative value(https://www.php.net/manual/en/function.crc32.php))

$sortArrFromSeed = function($arr, $seed){
    $arrLen = count($arr);
    $newArr = [];
    
    $hash = crc32($seed); // returns hash (0-9 numbers)
    $hash = strval($hash);
    while(strlen($hash) < $arrLen){
    $hash .= $hash;
    }
    
    for ($i=0; $i<$arrLen; $i++) {
        $index = (int) $hash[$i] * (count($arr)/9); // because 0-9 range  
        $index = (int) $index; // remove decimal
        if($index !== 0) $index--;
    
        array_push($newArr, $arr[$index]);
        unset($arr[$index]);
        $arr = array_values($arr);
    }
    return $newArr;
};


// TESTING
$arr = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'];
$arr = $sortArrFromSeed($arr,'myseed123');
echo '<pre>'.print_r($arr,true).'</pre>';
proseosoc
  • 1,168
  • 14
  • 23
-2

This seems the easiest for me...

srand(123);
usort($array,function($a,$b){return rand(-1,1);});
SmallhillCZ
  • 152
  • 10
  • This does not yield the same random distribution as shuffle would since it doesn't compare two values consistently. In fact, it almost always leaves the array as is. See https://gist.github.com/pschultz/44bee17e7ffe9579aeb14747aa818fea – Peter Feb 06 '17 at 11:21