Does anyone know what's the randomness of PHP's shuffle() function? Does it depend on the operating system?
Does it use PHP's own seeder?
Is it possible to use mt_rand() as generator?
shuffle() function is based on the same generator as rand(), which is the system generator based on linear congruential algorithm. This is a fast generator, but with more or less randomness. Since PHP 4.2.0, the random generator is seeded automatically, but you can use srand() function to seed it if you want.
mtrand() is based on Mersenne Twister algorithm, which is one of the best pseudo-random algorithms available. To shuffle an array using that generator, you'd need to write you own shuffle function. You can look for example at Fisher-Yates algorithm. Writing you own shuffle function will yield to better randomness, but will be slower than the builtin shuffle function.
Update for PHP 7.1
Since the rng_fixes rfc was implemented for PHP 7.1, the implementation of shuffle now utilizes the Mersenne Twister PRNG (i.e. it uses mt_rand and is affected by calling mt_srand).
The legacy system PRNG (rand) is no longer available; the functions rand and srand are in fact aliased to their mt_ equivalents.
Based on Mirouf's answer (thank you so much for your contribution)... I refined it a little bit to take out redundant array counting. I also named the variables a little differently for my own understanding.
If you want to use this exactly like shuffle(), you could modify the parameter to be passed by reference, i.e. &$array, then make sure you change the return to simply: "return;" and assign the resulting random array back to $array as such:
$array = $randArr; (Before the return).
function mt_shuffle($array) {
$randArr = [];
$arrLength = count($array);
// while my array is not empty I select a random position
while (count($array)) {
//mt_rand returns a random number between two values
$randPos = mt_rand(0, --$arrLength);
$randArr[] = $array[$randPos];
/* If number of remaining elements in the array is the same as the
* random position, take out the item in that position,
* else use the negative offset.
* This will prevent array_splice removing the last item.
*/
array_splice($array, $randPos, ($randPos == $arrLength ? 1 : $randPos - $arrLength));
}
return $randArr;
}
It's random just like rand();
And as PHP style you don't need to seed
mt_rand()
Generates a random number.
shuffle()
Randomizes an array. It also generates new keys in the array rather than just rearranging the old ones.
If you want to seed in PHP you would have used mt_strand().
However, since PHP 4.2.0 seeding is done automatically in PHP when you call mt_rand.
Works with associative and numeric arrays:
function mt_shuffle_array($array) {
$shuffled_array = [];
$arr_length = count($array);
if($arr_length < 2) {
return $array;
}
while($arr_length) {
--$arr_length;
$rand_key = array_keys($array)[mt_rand(0, $arr_length)];
$shuffled_array[$rand_key] = $array[$rand_key];
unset($array[$rand_key]);
}
return $shuffled_array;
}
$array = [-2, -1, 'a' => '1', 'b' => '2', 'c' => '3', 11, 'd' => '4', 22];
$shuffled_array = mt_shuffle_array($array);
I've created a function who sort my array randomly.
/**
* Build a random array
*
* #param mixed $array
*
* #return array
*/
function random_array($array) {
$random_array = array();
// array start by index 0
$countArray = count($array) - 1;
// while my array is not empty I build a random value
while (count($array) != 0) {
//mt_rand return a random number between two value
$randomValue = mt_rand(0, $countArray);
$random_array[] = $array[$randomValue];
// If my count of my tab is 4 and mt_rand give me the last element,
// array_splice will not unset the last item
if(($randomValue + 1) == count($array)) {
array_splice($array, $randomValue, ($randomValue - $countArray + 1));
} else {
array_splice($array, $randomValue, ($randomValue - $countArray));
}
$countArray--;
}
return $random_array;
}
It's not the best way to do that but when I've used the function shuffle, it was always returning a random array in the same order. If this could help someone, I will be happy !
Related
I'm trying to create a random number generator in PHP. It's supposed to generate three (3) numbers at a time, without repeat. That's to say, the 3 numbers cannot be the same.
Here's what I've tried so far:
$array = [];
$A = mt_rand(1,36);
$array[0] = $A;
$B = mt_rand(1,36);
$array[1] = $B;
if(in_array($B,$array)){
$B = mt_rand(1,36);
$array[1] = $B;
}
$C = mt_rand(1,36);
$array[2] = $C;
if(in_array($C,$array)){
$C = mt_rand(1,36);
$array[2] = $C;
}
$length = count($array);
//display the array values;
for($i = 0; $i < $length; $i++){
echo ($array[$i]."<br>");
}
Can anyone tell me where I'm going wrong?
Like this ( as per my initial comment ),
$array = [];
while( count($array) < 3 ){
$rand = mt_rand(1,36);
$array[$rand] = $rand;
}
print_r( $array );
By setting the "key" to be the random number, we can abuse the fact that associative array keys are unique. Then it's a simple matter of waiting until the array contains the desired amount of unique items.
You can test it here
Outputs: ( your results may vary, it's random )
Array
(
[16] => 16
[20] => 20
[27] => 27
)
UPDATE I was trying to think of a valid way to do it without using a loop ( on my way home from work ), and this way may be even better in some cases.
$a = range(1,36);
shuffle($a);
$array = array_slice($a, 0, 3);
print_r($array);
This will have better performance when the number of items you must find is higher. This is because there is no repetition, no collisions. So if you have a small range but need to find many items for the return, this will preform better. If you have many items and need to return only few, then the first one may be better, if not from speed then from memory use.
You can see it here
For reference this uses
range() - Create an array containing a range of elements.
http://php.net/manual/en/function.range.php
shuffle() - Shuffles (randomizes the order of the elements in) an array. It uses a pseudo random number generator that is not suitable for cryptographic purposes.
http://php.net/manual/en/function.shuffle.php
array_slice() - Returns the sequence of elements from the array as specified by the offset and length parameters.
http://php.net/manual/en/function.array-slice.php
So to explain this last one
First we create an array that contains each of our possible numbers as an element. So for example like this [1,2,3,4,5,6, ...].
Next we shuffle it which randomizes the order of the whole array. Shuffle modifies the array by "reference" so it doesn't return our array and therefor there is no assignment ( I think it returns Boolean, however I'm at a loss as to how it could fail and return false, pretty much it just returns true which we don't want to overwrite our array with ). So our example then becomes this [16,20,27,14,5,1, ...]
Last we cut out the number of items we need to return. Finally we end the example with this [16,20,27];
You can crunch the first one down into one ( really 2) line by assigning the value of the $rand variable in the condition of the loop. Like this:
$array = [];
while( count($array) < 3 && false !== ($rand = mt_rand(1,36))) $array[$rand] = $rand;
Because mt_rand(1,36) will never return boolan false. Also if I remember mt_rand is the same as rand now, or at least in current PHP versions.
Note: As of PHP 7.1.0, rand() uses the same random number generator as mt_rand(). To preserve backwards compatibility rand() allows max to be smaller than min as opposed to returning FALSE as mt_rand(). http://php.net/manual/en/function.rand.php
Hope it helps you, remember to think outside of the box.
Looking the function below:
function CustomShuffle($arr, $para){
............................
............................
return $array;
}
Suppose this is an array:
$array = array("red","green","blue","yellow","purple");
looking output something like below (May be different ordered but must be same for same integer parameter)
$result = CustomShuffle($array, 10);
// output: array("blue","purple","yellow","red","green") same
$result = CustomShuffle($array, 12);
// output: array("purple","yellow","red","green","blue")
$result = CustomShuffle($array, 10);
// output: array("blue","purple","yellow","red","green") same
$result = CustomShuffle($array, 7);
// output: array("blue","yellow","purple","red","green")
Simply, array will be shuffled with respect to integer parameter but output will be same for same parameter. Is it possible?
Yes this is possible, how it happens does come down to a desired implementation and how many permutations you wish to allow. A very naive method of accomplishing this is to have a loop that runs $para times within CustomShuffle that would array_shift() an element then array_push() that same element. This method would only give you count($array) possible outcomes, meaning numbers congruent modulo count($array) would produce the same result.
The optimal algorithm would allow you to take advantage of the maximum combinations, which would be gmp_fact(count($array)), or simply the factorial of the length of the input array. There is no possible way to achieve more unique combinations than this value, so no matter what algorithm you design, you will always have a constraint on the value of $para until you eventually encounter a combination already seen.
How can I make the php shuffle function use a seed, so that when I use the same seed, the shuffle function will output the same array. I read that shuffle is automatically seeded. Is there a way to get the seed of that shuffle used, or how can I create/mimic shuffle with a custom seed?
You can't retrieve the seed used by shuffle, but you can simulate shuffle and fix your own seed:
$array = range(1, 10);
function seededShuffle(array &$array, $seed) {
mt_srand($seed);
$size = count($array);
for ($i = 0; $i < $size; ++$i) {
list($chunk) = array_splice($array, mt_rand(0, $size-1), 1);
array_push($array, $chunk);
}
}
$seed = date('Ymd');
seededShuffle($array, $seed);
var_dump($array);
This will set a different seed each day, but throughout the day it will use the same seed and shuffle the array in the same order; tomorrow will be a different random shuffle to today
For today (6th June 2015), the sequence should be
3, 6, 9, 2, 7, 1, 8, 5, 10, 4
PHP does not have shuffling with seeding, but you can do this instead:
$an_array = array('a','b','c','d');
$indices = array(0,1,2,3);
// shuffle the indices and use them as shuffling seed
shuffle($indices);
// then whenever you want to produce exactly same shuffle use the pre-computed shuffled indices
function shuffle_precomputed($a, $shuffled_indices)
{
$b = $a; // copy array
foreach ($shuffled_indices as $i1=>$i2) $a[$i2] = $b[$i1];
return $a;
}
use like this:
$shuffled_array = shuffle_precomputed($an_array, $indices);
You can even use the factoradic number system to transform the $shuffled_indices array to/from a unique integer number that can be used as a unique seed, then simply compute the shuffle from the factoradic number to be used in shuffle_precomputed function.
For additional shuffle variations for PHP you may want to see:
PHP - shuffle only part of an array
Efficiently pick n random elements from PHP array (without shuffle)
cant shuffle splfixedarray why so ? what is the possible why to shuffle splfixedarray
<?php
$cfix = new SplFixedArray(5);
$cfix[0]=1;
$cfix[1]=4;
$cfix[2]=2;
$cfix[3]=9;
$cfix[4]=3;
print_r($cfix);
//cant shuffle splfixedarray why so ?
shuffle($cfix);
?>
I needed to shuffle a large fixed array (> 100,000,000 elements) so using the fixed array is necessary for a huge reduction in memory usage compared to an array. A 100,000,000 element array of integers in PHP would require approximately 13.7 GB of memory where SplFixedArray is about 5.3 GB for the same list.
Since casting that large of an SplFixedArray to an array has an unreasonable memory cost (and defeats the purpose of using the fixed array in the first place), I wrote this function to randomize elements in place.
Compared to calling shuffle() on an array, it's only marginally slower (0.48s vs. 0.32s using shuffle on an SplFixedArray vs array of 1,000,000 elements).
/**
* Shuffle implementation for an SplFixedArray. Like PHP's shuffle(array)
* function, this randomizes the elements in the supplied array.
*
* #param \SplFixedArray $array The array to shuffle in place
* #param int $passes Number of passes over each element to randomize
* #return bool true on success
*/
function shuffle_spl_fixed_array(\SplFixedArray &$array, $passes = 1)
{
$size = $array->count();
for ($pass = 0; $pass < $passes; ++$pass) {
for ($idx = 0; $idx < $size; ++$idx) {
$swap = mt_rand(0, $size - 1);
$temp = $array[$swap];
$array[$swap] = $array[$idx];
$array[$idx] = $temp;
}
}
return true;
}
Increase the number of passes of make multiple calls depending on the amount of randomness needed, but I found one pass worked well enough for my purposes, especially if you can then pick from it at random.
It's because shuffle expects an array as parameter.
shuffle() expects parameter 1 to be array, object given..
Workaround using toArray and fromArray:
$cfix = new SplFixedArray(5);
$cfix[0]=1;
$cfix[1]=4;
$cfix[2]=2;
$cfix[3]=9;
$cfix[4]=3;
print_r($cfix);
$array = $cfix->toArray();
shuffle($array);
$cfix = SplFixedArray::fromArray($array);
print_r($cfix);
cant shuffle splfixedarray why so ?
Because $cfix is an object in the form of an array. shuffle() works only on arrays, not on objects. The solution would be to cast the object to an array.
Try like this
<?php
$cfix = new SplFixedArray(5);
$cfix[0]=1; $cfix[1]=4; $cfix[2]=2; $cfix[3]=9; $cfix[4]=3;
$cfix=(array)$cfix; //Casting the $cifx object to an array
shuffle($cfix);
print_r($cfix);
SO,
The problem
It's well known about pseudo-random numbers. 'Pseudo' actually means, that, despite they are random (i.e. unpredictable) in general, they still will be same in sequence, in which same generator init value was used. For example, in PHP there's mt_srand() function to do that. Example:
mt_srand(1);
var_dump(mt_rand(), mt_rand(), mt_rand());
-no matter, how many time we'll launch our script: generated three numbers will always be same in sequence.
Now, my issue is how to do the same - but for shuffling array. I.e. I want to create a function, which will accept input array to shuffle and seed. Within same seed value shuffling must have consecutive same order. I.e. let we call that function shuffleWithSeed() - and then following should work for every script launch:
$input = ['foo', 'bar', 'baz'];
$test = shuffleWithSeed($input, 1000);//1000 is just some constant value
var_dump($test); //let it be ['bar', 'foo', 'baz']
$test = shuffleWithSeed($test, 1000);
var_dump($test); //let it be ['baz', 'foo', 'bar']
$test = shuffleWithSeed($test, 1000);
var_dump($test); //let it be ['baz', 'bar', 'foo']
//...
-i.e. no matter how many times we'll do shuffle for our array - I want for the next script launch ordering sequence will be always the same within one seed value.
My approach
I have in mind this algorithm:
Initialize random numbers generator with passed seed
Generate N random numbers, where N is the number of $input members
Sort numbers from step 2
Make corresponding numbers be dependent from $input keys.
I've implemented this in:
function shuffleWithSeed(array $input, $seed=null)
{
if(!isset($seed))
{
shuffle($input);
return $input;
}
if(!is_int($seed))
{
throw new InvalidArgumentException('Invalid seed value');
}
mt_srand($seed);
$random = [];
foreach($input as $key=>$value)
{
$random[$key] = mt_rand();
}
asort($random);
$random = array_combine(array_keys($random), array_values($input));
ksort($random);
return $random;
}
-now, also found Fisher-Yates algorithm - but not sure if it can work with pseudorandom numbers (i.e. with seed)
The question
As you can see, I'm doing two sorts in my function - first by values and second by keys.
Can this be done with one sort? Or without sort at all? Input array could be large, so I want to avoid this.
However, may be my algorithm is not well? If yes, what other options could be suggested?
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.