Randomize a PHP Array with a Seed

Randomize a PHP array with a seed?

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.

How can I randomize an array in PHP by providing a seed and get the same order?

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);
$items = array_values($items);
for ($i = count($items) - 1; $i > 0; $i--)
{
$j = @mt_rand(0, $i);
$tmp = $items[$i];
$items[$i] = $items[$j];
$items[$j] = $tmp;
}
}

Same function for a string in php7

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

Generate predictable-suffled random array

Here's a copy and paste of a function I have implemented a while ago for exactly this purpose:

/**
* Shuffles an array in a repeatable manner, if the same $seed is provided.
*
* @param array &$items The array to be shuffled.
* @param integer $seed The result of the shuffle will be the same for the same input ($items and $seed). If not given, uses the current time as seed.
* @return void
*/
protected function seeded_shuffle(array &$items, $seed = false) {
$items = array_values($items);
mt_srand($seed ? $seed : time());
for ($i = count($items) - 1; $i > 0; $i--) {
$j = mt_rand(0, $i);
list($items[$i], $items[$j]) = array($items[$j], $items[$i]);
}
}

It implements a simple Fisher-Yates shuffle with a seeded random number generator.

Shuffling array based on seed to get always the same result?

If you need it on any pc, you need a random number generator that works the same across computers. I looked up one pseudo-random-number generator that might be suitable for you from the Random Number Generation Wikipedia page and put it into a class so you can seed it.

I don't know for what you need it, but this just might suit your needs. It's independent to system configuration:

function shuffleIt($array, $seed)
{
$mwc = new mwc($seed);
$order = array();
$count = count($array);
while($count--)
$order[] = $mwc->random()
;

array_multisort($order, $array);
return $array;
}

/**
* Multiply-with-carry RNG
*
* method invented by George Marsaglia
*/
class mwc
{
private static $def_m_w = 1712; /* must not be zero */
private static $def_m_z = 23; /* must not be zero */
private $m_w, $m_z;
public function __construct($seed = NULL)
{
$this->m_w = self::$def_m_w;
$this->m_z = self::$def_m_z;
if (NULL !== $seed)
$this->seed($seed);
}
public function seed($seed)
{
$seed = (int) $seed;
if (!$seed) throw new InvalidArgumentException('Must not be zero.');
$this->m_z = $seed;
$this->random();
}
public function random()
{
$this->m_z = 36969 * ($this->m_z & 65535) + ($this->m_z >> 16);
$this->m_w = 18000 * ($this->m_w & 65535) + ($this->m_w >> 16);
return ($this->m_z << 16) + $this->m_w; /* 32-bit result */
}
}

Note: This might behave differently between 32/64 bit systems, especially as PHP differs here for integers and overflows between windows and unix. You might want offset at the signed minimum for 32 bit integers in PHP instead of 0 as it is now, to switch the implementation to gmp or just reduce the size by one bit.


Usage Example 32 bit reported to work by ekke from netherlands

$shuffle = new GeorgeShuffle();
$seed = $shuffle->seed();
$a = array('A', 'B', 'C', 'D', 'E', 'F', 'G');
$shuffle->reOrder($a);
var_dump($a);
$shuffle->seed($seed);
$shuffle->reOrder($a);
var_dump($a);

/**
* Array shuffle class using
* the multiply-with-carry method
* invented by George Marsaglia
*/
class GeorgeShuffle
{

private static $def_m_w = 1959; /* must not be zero */
private static $def_m_z = 2006; /* must not be zero */
private $m_w, $m_z;
const maxint = 2147483647;

public function __construct($seed = null)
{
$this->m_w = self::$def_m_w;
$this->m_z = self::$def_m_z;
if ($seed) $this->seed($seed);
}

public function reOrder(&$array, $seed = null)
{
if (!empty($seed)) $this->seed($seed);
$a = array();
for ($i = 0, $j = count($array); $i < $j; $i++) {
$a[$i] = $this->random();
}
array_multisort($a, $array);
//- to return a copy, remove the &
return $array;
}

public function seed($seed = false)
{
if (is_string($seed)) $seed = hexdec($seed);
if (empty($seed)) $seed = round(mt_rand(1, self::maxint));
$this->m_z = $seed;
$this->random();
//- return the seed used in hex (8 chars) for reproducing
return str_pad(dechex($seed), 8, '0', STR_PAD_LEFT);
}

public function random()
{
$this->m_z = 36969 * (($this->m_z And 65535) + ($this->m_z >> 16));
$this->m_w = 18000 * (($this->m_w And 65535) + ($this->m_w >> 16));
return ($this->m_z << 16) + $this->m_w; /* 32-bit signed result */
}
}

Use a String as a Seed for a Randomiser

You can calculate the hash of the input string to get an integer (with crc32() in example) and then, use that number as seed :

<?php
$StringSeed = "John";
$IntSeed = crc32($StringSeed);

echo "Hash Value : $IntSeed" . PHP_EOL;

srand($IntSeed);

echo rand(1, 10); // 8
echo rand(1, 10); // 4
echo rand(1, 10); // 10

Try it yourself

Can a seeded shuffle be reversed?

Turns out the answer is yes, and pretty simple:

function seeded_unshuffle(array &$items, $seed) {
$items = array_values($items);

mt_srand($seed);
$indices = [];
for ($i = count($items) - 1; $i > 0; $i--) {
$indices[$i] = mt_rand(0, $i);
}

foreach (array_reverse($indices, true) as $i => $j) {
list($items[$i], $items[$j]) = [$items[$j], $items[$i]];
}
}

Just generate the same random number sequence using the known seed, and traverse it in reverse.

Random number within range with a seed

mt_srand() is used to set your seed and mt_rand() takes in a min and max if you want to set the range. Basically something like:

mt_srand($seed);
mt_rand($min, $max);

Note: As of PHP 4.2.0, there is no need to seed the random number generator with srand() or mt_srand() as this is now done automatically.



Related Topics



Leave a reply



Submit