开发者

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

开发者 https://www.devze.com 2023-04-13 07:04 出处:网络
I need to shuffle an array based on a seed number so I can get the same shuffle if I need it. For example:

I need to shuffle an array based on a seed number so I can get the same shuffle if I need it.

For example:

1. print_r( shuffleIt( $array, 2 ) );
2. print_r( shuffleIt( $array, 6 ) );
3. print_r( shuffleIt( $array, 2 ) );
  1. and 3. would show the same shuffled array but different than 2.

I found this function googling:

function entropy( $array, $sort_seed ) {
    mt_srand( $sort_seed );
    $order = array_map( create_function( '$val', 'return mt_rand( );' ), range( 1, count( $array ) ) );
    arra开发者_如何学Pythony_multisort( $order, $array );
    return $array;
}

It works fine on my pc with php-cli, I always get the same array for each different sort_seed I use, but when I uploaded it to a server, I get different arrays every time even when I am using the same sort_seed.

How could I get always the same shuffled array when using the same sort_seed?

btw. I need to preserve keys or sort a multidimensional array so I can store the key there.


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 */
    }
}


Does you host run by chance the Suhosin security extension? Apparently, it has a couple of directives that prevent scripts from setting a seed:

suhosin.srand.ignore = On
suhosin.mt_srand.ignore = On

Further reading:

  • http://www.suspekt.org/2008/08/22/suhosin-0926-improved-randomness/
  • http://www.adayinthelifeof.nl/2010/12/13/php-srand-problems-with-suhosin/

Here's a quick way to test whether this is the problem:

<?php

mt_srand(33);
var_dump(mt_rand(1, 10000));

mt_srand(33);
var_dump(mt_rand(1, 10000));

mt_srand(33);
var_dump(mt_rand(1, 10000));
0

精彩评论

暂无评论...
验证码 换一张
取 消