Randomiser un tableau PHP avec une graine?
Je cherche une fonction à laquelle je peux passer un tableau et une graine en PHP et récupérer un tableau "randomisé". Si je transmettais à nouveau le même tableau et la même graine, j'obtiendrais la même sortie.
J'ai essayé ce 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);
Mais cela ne semble pas fonctionner. Des pensées sur la meilleure façon de le faire?
Cette question danse autour du problème mais elle est ancienne et personne n'a fourni de réponse réelle sur la façon de le faire: Puis-je randomiser un tableau en fournissant une graine et obtenir le même commande? - "Oui" - mais comment?
Mettre à jour
Les réponses jusqu'à présent fonctionnent avec PHP 5.1 et 5.3, mais pas 5.2. Il se trouve que la machine sur laquelle je veux exécuter ceci utilise 5.2.
Quelqu'un peut-il donner un exemple sans utiliser mt_rand? Il est "cassé" en PHP 5.2 car il ne donnera pas la même séquence de nombres aléatoires basés sur la même graine. Voir la page php mt_rand et le bug tracker pour en savoir plus sur ce problème.
7 réponses
Vous pouvez utiliser array_multisort
pour commander le tableau de valeurs par un second tableau de mt_rand
valeurs:
$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);
Ici $order
est un tableau de mt_rand
les valeurs de la même longueur que $arr
. array_multisort
trie les valeurs de $order
et ordonne les éléments de $arr
selon l'ordre des valeurs de $order
.
Désolé, mais en conséquence à le documentation Le la fonction de lecture aléatoire est ensemencée automatiquement.
Normalement, vous ne devriez pas essayer de trouver vos propres algorithmes pour randomiser les choses car elles sont très susceptibles d'être biaisées. Le Fisher-Yates algorithme est connu pour être à la fois efficace et impartiale si:
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;
}
}
Exemple (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
)
Le problème que vous avez est que PHP est livré avec deux générateurs de nombres aléatoires intégrés.
La commande shuffle()
n'utilise pas le générateur de nombres aléatoires mt_rand()
; elle utilise l'ancien générateur de nombres aléatoires rand()
.
Par conséquent, si vous voulez que shuffle()
utilise une séquence numérique ensemencée, vous devez ensemencer l'ancien randomiseur, en utilisant srand()
plutôt que mt_srand()
.
Dans la plupart des autres cas, vous devriez utiliser mt_rand()
plutôt que rand()
, car c'est un meilleur générateur de nombres aléatoires.
La question principale comporte deux parties. L'un est sur la façon de mélanger. L'autre est sur la façon d'ajouter au hasard.
Une solution simple
C'est probablement la réponse la plus simple à la question principale. C'est suffisant pour la plupart des cas dans les scripts PHP. Mais pas tous (voir ci-dessous).
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;
}
La méthode ci-dessus fera l'affaire, même si elle ne produit pas de true random shuffles pour toutes les combinaisons possibles de seed-array. Cependant, si vous voulez vraiment qu'il soit équilibré et tout, je suppose PHP ne doit pas être votre choix.
Une solution plus utile pour les programmeurs avancés
Comme L'a déclaré André Laszlo, la randomisation est une affaire délicate. Il est généralement préférable de laisser un objet dédié le gérer. Mon point est que vous ne devriez pas avoir à vous soucier du caractère aléatoire lorsque vous écrivez la fonction shuffle. Selon le degré de ramdomness que vous souhaitez dans votre lecture aléatoire, vous pouvez avoir un certain nombre D'objets PseudoRandom à choisir. Ainsi ce qui précède pourrait ressembler à ce:
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;
}
Maintenant, cette solution est celle pour laquelle je voterais. Il sépare les codes de lecture aléatoire des codes de randomisation. Selon le type de hasard dont vous avez besoin, vous pouvez sous-classer PseudoRandom, ajouter les méthodes nécessaires et vos formules préférées. Et, comme la même fonction de lecture aléatoire peut être utilisée avec de nombreux algorithmes Aléatoires, un algorithme aléatoire peut être utilisé à différents endroits.
Dans les versions récentes de PHP, l'ensemencement des fonctions intégrées de PHP rand()
et mt_rand()
Ne vous donnera pas les mêmes résultats à chaque fois. La raison de ceci n'est pas claire pour moi (pourquoi voudriez-vous semer la fonction de toute façon si le résultat est différent à chaque fois.) De toute façon, il semble que la seule solution est de écrire votre propre fonction aléatoire
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;
}
}
Utilisation:
// set seed
Random::seed(42);
// echo 10 numbers between 1 and 100
for ($i = 0; $i < 10; $i++) {
echo Random::num(1, 100) . '<br />';
}
Le code ci-dessus affichera la séquence suivante chaque fois que Vous l'exécutez:
76
86
14
79
73
2
87
43
62
7
Juste changer la graine pour obtenir une séquence "aléatoire" complètement différente
Une variante qui fonctionne également avec PHP version 7.2, car la fonction PHP create_function est obsolète dans la dernière version de php.
mt_srand($seed);
$getMTRand = function () {
return mt_rand();
};
$order = array_map($getMTRand, range(1, count($array)));
array_multisort($order, $array);
return $array;
Cela me semble le plus facile...
srand(123);
usort($array,function($a,$b){return rand(-1,1);});