web-dev-qa-db-fra.com

Générer des résultats aléatoires en poids en PHP?

Je sais comment générer un nombre aléatoire dans PHP, mais disons que je veux un nombre aléatoire compris entre 1 et 10 mais que je veux davantage de 3,4,5 puis 8,9,10. Comment est-ce possible? Je publierais ce que j'ai essayé, mais honnêtement, je ne sais même pas par où commencer.

51
kyct

Basé sur @/Allain's answer / link , j'ai développé cette fonction rapide en PHP. Vous devrez le modifier si vous souhaitez utiliser une pondération non entière.

  /**
   * getRandomWeightedElement()
   * Utility function for getting random values with weighting.
   * Pass in an associative array, such as array('A'=>5, 'B'=>45, 'C'=>50)
   * An array like this means that "A" has a 5% chance of being selected, "B" 45%, and "C" 50%.
   * The return value is the array key, A, B, or C in this case.  Note that the values assigned
   * do not have to be percentages.  The values are simply relative to each other.  If one value
   * weight was 2, and the other weight of 1, the value with the weight of 2 has about a 66%
   * chance of being selected.  Also note that weights should be integers.
   * 
   * @param array $weightedValues
   */
  function getRandomWeightedElement(array $weightedValues) {
    $Rand = mt_Rand(1, (int) array_sum($weightedValues));

    foreach ($weightedValues as $key => $value) {
      $Rand -= $value;
      if ($Rand <= 0) {
        return $key;
      }
    }
  }
89
Brad

Pour un nombre aléatoire efficace biaisé systématiquement vers une extrémité de l'échelle:

  • Choisissez un nombre aléatoire continu compris entre 0..1
  • Élever à un pouvoir γ, pour le biaiser. 1 n'est pas pondéré, plus bas donne plus de nombres élevés et vice versa
  • Échelle à la plage désirée et arrondi à l'entier

par exemple. dans PHP (non testé):

function weightedrand($min, $max, $gamma) {
    $offset= $max-$min+1;
    return floor($min+pow(lcg_value(), $gamma)*$offset);
}
echo(weightedrand(1, 10, 1.5));
27
bobince

Il y a un très bon tutoriel pour vous .

Fondamentalement:

  1. Faites la somme des poids de tous les nombres. 
  2. Choisissez un nombre aléatoire inférieur à celui
  3. soustrayez les poids dans l’ordre jusqu’à ce que le résultat soit négatif et renvoyez ce nombre s’il l’est.
21
Allain Lalonde

Le hack naïf pour cela serait de construire une liste ou un tableau comme

1, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 9, 9, 10, 10

Et puis sélectionnez au hasard à partir de cela.

11
Iain Holder

Ce tutoriel vous le guide, en PHP, avec plusieurs solutions de copier/coller. Notez que cette routine est légèrement modifiée par rapport à ce que vous trouverez sur cette page, à la suite du commentaire ci-dessous.

Une fonction extraite du post:

/**
 * weighted_random_simple()
 * Pick a random item based on weights.
 *
 * @param array $values Array of elements to choose from 
 * @param array $weights An array of weights. Weight must be a positive number.
 * @return mixed Selected element.
 */

function weighted_random_simple($values, $weights){ 
    $count = count($values); 
    $i = 0; 
    $n = 0; 
    $num = mt_Rand(1, array_sum($weights)); 
    while($i < $count){
        $n += $weights[$i]; 
        if($n >= $num){
            break; 
        }
        $i++; 
    } 
    return $values[$i]; 
}
5
Brad Parks

Vous pouvez utiliser weightedChoice from Bibliothèque PHP non standard . Il accepte une liste de paires (élément, poids) pour pouvoir travailler avec des éléments qui ne peuvent pas être des clés de tableau. Vous pouvez utiliser pairs function pour convertir array(item => weight) au format requis.

use function \nspl\a\pairs;
use function \nspl\rnd\weightedChoice;

$weights = pairs(array(
    1 => 10,
    2 => 15,
    3 => 15,
    4 => 15,
    5 => 15,
    6 => 10,
    7 => 5,
    8 => 5,
    9 => 5,
    10 => 5
));

$number = weightedChoice($weights);

Dans cet exemple, 2-5 apparaîtra 3 fois plus souvent que 7-10.

2
Ihor Burlachenko

Clair et juste . Il suffit de copier/coller et de le tester.

/**
 * Return weighted probability
 * @param (array) prob=>item 
 * @return key
 */
function weightedRand($stream) {
    $pos = mt_Rand(1,array_sum(array_keys($stream)));           
    $em = 0;
    foreach ($stream as $k => $v) {
        $em += $k;
        if ($em >= $pos)
            return $v;
    }

}

$item['30'] = 'I have more chances than everybody :]';
$item['10'] = 'I have good chances';
$item['1'] = 'I\'m difficult to appear...';

for ($i = 1; $i <= 10; $i++) {
    echo weightedRand($item).'<br />';
}

Edit: Ajout du support manquant à la fin.

2
Nick Olszanski

Depuis que j'ai utilisé la solution de IainMH, je peux aussi bien partager mon code PHP:

<pre><?php

// Set total number of iterations
$total = 1716;

// Set array of random number
$arr = array(1, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5);
$arr2 = array(0, 0, 1, 1, 2, 2, 2, 3, 3, 4, 5);

// Print out random numbers
for ($i=0; $i<$total; $i++){

    // Pick random array index
    $Rand = array_Rand($arr);
    $Rand2 = array_Rand($arr2);

    // Print array values
    print $arr[$Rand] . "\t" . $arr2[$Rand2] . "\r\n";

}

?></pre>
1
degenerate

fonction getBucketFromWeights ($ valeurs) { $ total = $ currentTotal = $ bucket = 0;

foreach ($values as $amount) {
    $total += $amount;
}

$Rand = mt_Rand(0, $total-1);

foreach ($values as $amount) {
    $currentTotal += $amount;

    if ($Rand => $currentTotal) {
        $bucket++;
    }
    else {
        break;
    }
}

return $bucket;

}

Je ugh modifié ceci à partir d'une réponse ici Choisir un élément aléatoire en fonction des pondérations définies par l'utilisateur

Après avoir écrit cela, j'ai vu que quelqu'un d'autre avait une réponse encore plus élégante. Il il il il.

0
user4951

Je viens de publier une classe pour effectuer un tri pondéré facilement.

Il est basé sur le même algorithme que celui mentionné dans Brad's et Allain's answers et est optimisé pour la vitesse, testé pour une distribution uniforme et prend en charge les éléments de tout type PHP.

Son utilisation est simple. Instanciez-le:

$picker = new Brick\Random\RandomPicker();

Ajoutez ensuite des éléments sous forme de tableau de valeurs pondérées (uniquement si vos éléments sont des chaînes ou des entiers):

$picker->addElements([
    'foo' => 25,
    'bar' => 50,
    'baz' => 100
]);

Ou utilisez des appels individuels vers addElement(). Cette méthode supporte tout type de valeur PHP en tant qu'éléments (chaînes, nombres, objets, ...), par opposition à l'approche tableau:

$picker->addElement($object1, $weight1);
$picker->addElement($object2, $weight2);

Ensuite, obtenez un élément aléatoire:

$element = $picker->getRandomElement();

La probabilité d'obtenir l'un des éléments dépend de son poids. La seule restriction est que les poids doivent être des entiers.

0
Benjamin

Beaucoup de réponses sur cette page semblent utiliser des ballonnements de tableaux, une itération excessive, une bibliothèque ou un processus difficile à lire. Bien sûr, tout le monde pense que son propre bébé est le plus mignon, mais honnêtement, je pense que mon approche est maigre, simple et facile à lire/à modifier ...

Conformément à l'OP, je vais créer un tableau de valeurs (déclarées en tant que clés) de 1 à 10, 3, 4 et 5 représentant le double du poids des autres valeurs (déclarées en tant que valeurs).

$values_and_weights=array(
    1=>1,
    2=>1,
    3=>2,
    4=>2,
    5=>2,
    6=>1,
    7=>1,
    8=>1,
    9=>1,
    10=>1
);

Si vous ne faites qu'une sélection aléatoire et/ou si votre tableau est relativement petit * (faites votre propre analyse comparative pour en être sûr), c'est probablement votre meilleur pari:

$pick=mt_Rand(1,array_sum($values_and_weights));
$x=0;
foreach($values_and_weights as $val=>$wgt){
    if(($x+=$wgt)>=$pick){
        echo "$val";
        break;
    }
}

Cette approche n'implique aucune modification du tableau et n'aura probablement pas besoin d'itérer le tableau entier (mais peut l'être).


D'autre part, si vous voulez faire plus d'une sélection aléatoire sur le tableau et/ou que votre tableau est suffisamment grand * (effectuez votre propre analyse comparative pour en être sûr), la restructuration du tableau peut s'avérer plus efficace.

Le coût en mémoire pour générer un nouveau tableau sera de plus en plus justifié comme suit:

  1. la taille du tableau augmente et
  2. le nombre de sélections aléatoires augmente.

Le nouveau tableau nécessite le remplacement de "poids" par une "limite" pour chaque valeur en ajoutant le poids de l'élément précédent au poids de l'élément actuel.

Puis retournez le tableau de sorte que les limites correspondent aux clés du tableau et que les valeurs correspondent aux valeurs du tableau . La logique est la suivante: la valeur sélectionnée aura la limite la plus basse qui est> = $ pick.

// Declare new array using array_walk one-liner:
array_walk($values_and_weights,function($v,$k)use(&$limits_and_values,&$x){$limits_and_values[$x+=$v]=$k;});

//Alternative declaration method - 4-liner, foreach() loop:
/*$x=0;
foreach($values_and_weights as $val=>$wgt){
    $limits_and_values[$x+=$wgt]=$val;
}*/
var_export($limits_and_values);

Crée ce tableau:

array (
  1 => 1,
  2 => 2,
  4 => 3,
  6 => 4,
  8 => 5,
  9 => 6,
  10 => 7,
  11 => 8,
  12 => 9,
  13 => 10,
)

Maintenant, générez le $pick aléatoire et sélectionnez la valeur:

// $x (from walk/loop) is the same as writing: end($limits_and_values); $x=key($limits_and_values);
$pick=mt_Rand(1,$x);  // pull random integer between 1 and highest limit/key
while(!isset($limits_and_values[$pick])){++$pick;}  // smallest possible loop to find key
echo $limits_and_values[$pick];  // this is your random (weighted) value

Cette approche est brillante parce que isset() est très rapide et que le nombre maximal d'appels isset() dans la boucle while ne peut excéder le poids le plus élevé (à ne pas confondre avec la limite) dans le tableau. Pour ce cas, itérations maximum = 2!

CETTE APPROCHE N'A JAMAIS BESOIN DE RÉITER L'ENSEMBLE DU RÉSEAU

0
mickmackusa