4

I have a PHP script which needs to randomise an array with consistent results, so it can present the first few items to the user and they can then pull in more results from the same shuffled set if they want to.

What I'm currently using is this (based on the Fisher Yates algorithm I believe):

function shuffle(&$array, $seed)
{
    mt_srand($seed);
    for ($a=count($array)-1; $a>0; $a--) {
        $b = mt_rand(0, $a);
        $temp = $array[$a];
        $array[$a] = $array[$b];
        $array[$b] = $temp;
    }
}

Which works fine on my local installation, but the server it needs to run on has Suhosin installed, which overrides mt_srand, meaning the seed is ignored, the array is just randomly shuffled and the user gets duplicate results.

Everything I've found on Google suggests I need to disable suhosin.mt_srand.ignore (and suhosin.srand.ignore, not sure if the latter is relevant though) so I put the following in .htaccess:

php_flag suhosin.mt_srand.ignore Off
php_flag suhosin.srand.ignore Off

I have no access to php.ini on this server so AFAIK that's the only way I can do it. The problem is that has no effect - phpinfo() still shows both settings as On, whereas I can change other Suhosin settings using .htaccess no problem.

So I suppose what I'm looking for is either a way to actually disable suhosin.mt_srand.ignore (or a reason why it isn't working), or a workaround to seed a random number generator from within PHP. Or will I just have to implement another RNG myself?

Any help would be much appreciated. Thanks!

alexz
  • 96
  • 1
  • 5
  • Cache the resulting array somewhere, don't recreate it every request... – KingCrunch Feb 28 '12 at 13:05
  • Seems like that'd introduce a lot of issues for something that should be fairly simple though. By its nature it'd need to be reshuffled a lot (it's based on a search, each time the user searches it's reshuffled) and each shuffle would probably only be used 2 or 3 times max. It's a heavily used part of the site, so I'd be caching a *lot* of different shuffled versions of different result arrays for different users, probably in many cases the same results shuffled differently, and then I'd need to deal with storage and how long to retain each array and all that. – alexz Feb 28 '12 at 14:48
  • Is it really a requirement, that _every_ user _must_ see a _completely_ different version, or can't you just use one shuffled set for multiple users? – KingCrunch Feb 28 '12 at 18:38
  • That's possible, yeah, but if the *same* user repeats the same (or a similar) query, they should see a different version. Then at this point I'd still be storing multiple shuffles for multiple users, because, say, user A might still be getting results from shuffle A, whereas user B might have reshuffled and will be using shuffle B. Though I guess I could store a limited number of shuffles and just cycle through them. – alexz Feb 29 '12 at 09:50

1 Answers1

2

Using some basic maths and a few tricks you can quite easily create your OWN random function like i have just done :)

Sorry i haven't cleaned it up. It would be much better in a class as you could prevent the need to keep re-seeding it with the previous seed. Don't use a static variable as it limits you to only using 1 seed at a time (or manually keeping track of the seeds yourself). OOP would solve that. Do what you like with the function below, but let me know if you rewrite it.

/**
* returns a decimal between 0 and max_number, requires seeding every time and will ALWAYS return the same decimal for the same seed
* @copyright scott thompson, all rights reserved
* @license MIT (do what you like with this)
* @param string $seed
* @param int $max_number=100 adjust the maximum number range
*/
function random_number($seed, $max_number = 100) {

    //make sure there won't be any deadspace where random numbers will never fill
    if ($max_number > 0xFFFFFF) {
        trigger_error("Max random number was to high. Maximum number of " . 0xFFFFFF . " allowed. Defaulting to maximum number.", E_USER_WARNING);
        $max_number = 0xFFFFFF;
    }

    //hash the seed to ensure enough random(ish) characters each time
    $hash = sha1($seed);

    //use the first x characters, and convert from hex to base 10 (this is where the random number is obtain)
    $rand = base_convert(substr($hash, 0, 6), 16, 10);

    //as a decimal percentage (ensures between 0 and max number)
    return $rand / 0xFFFFFF * $max_number ;

}

$seed = 'hello';
print ($seed = random_number($seed)) . '<br />'; //66.779748605475
print ($seed = random_number($seed)) . '<br />'; //3.5753311857779
print ($seed = random_number($seed)) . '<br />'; //13.994396567011
print ($seed = random_number($seed)) . '<br />'; //70.344917198713
print ($seed = random_number($seed)) . '<br />'; //4.5583250855401

Hope this helps, scott

HenchHacker
  • 1,616
  • 1
  • 10
  • 16
  • Thanks, must admit though, it was my second approach >< first time i tried using the ord() value of each character in the hash but that gave me numbers only between 0.5 and 0.6 which i didn't see coming! So ended up taking a different approach by snipping off the front of the hash which just so happens to be a hex value... which worked and actually worked better than i had thought it would. – HenchHacker Oct 26 '12 at 00:22