web-dev-qa-db-fra.com

Générer un nombre aléatoire pondéré

J'essaie de trouver un (bon) moyen de choisir un nombre aléatoire dans une plage de nombres possibles, chaque poids de la plage ayant un poids. Pour le dire simplement: étant donné la gamme de nombres (0,1,2), choisissez un nombre où 0 a une probabilité de sélection de 80%, 1 a une chance de 10% et 2 a une chance de 10%.

Cela fait environ 8 ans depuis mon cours de statistiques universitaires, vous pouvez donc imaginer la formule appropriée pour que cela m’échappe pour le moment.

Voici la méthode «pas chère et sale» que j'ai proposée. Cette solution utilise ColdFusion. Vous pouvez utiliser la langue de votre choix. Je suis un programmeur, je pense que je peux gérer le porter. En fin de compte, ma solution doit être en Groovy - j'ai écrit celle-ci dans ColdFusion car il est facile d'écrire/tester rapidement dans CF.

public function weightedRandom( Struct options ) {

    var tempArr = [];

    for( var o in arguments.options )
    {
        var weight = arguments.options[ o ] * 10;
        for ( var i = 1; i<= weight; i++ )
        {
            arrayAppend( tempArr, o );
        }
    }
    return tempArr[ randRange( 1, arrayLen( tempArr ) ) ];
}

// test it
opts = { 0=.8, 1=.1, 2=.1  };

for( x = 1; x<=10; x++ )
{
    writeDump( weightedRandom( opts ) );    
}

Je cherche de meilleures solutions, veuillez suggérer des améliorations ou des alternatives.

38
Todd Sharp

L'échantillonnage de rejet (comme dans votre solution) est la première chose qui vous vient à l'esprit. Vous devez créer une table de correspondance avec des éléments renseignés par leur distribution de poids, puis choisir un emplacement aléatoire dans la table et la renvoyer. En tant que choix d'implémentation, je créerais une fonction d'ordre supérieur qui prend une spécification et renvoie une fonction qui renvoie des valeurs basées sur la distribution dans la spécification, de cette manière, vous éviterez de devoir construire la table pour chaque appel. L’inconvénient est que les performances algorithmiques de la construction de la table sont linéaires en fonction du nombre d’éléments et que la mémoire utilisée pour les spécifications volumineuses (ou celles comportant des membres avec des poids très faibles ou précis, par exemple, peut être importante), par exemple {0: 0.99999, 1 : 0,00001}). L'avantage est que le choix d'une valeur a une durée constante, ce qui peut être souhaitable si les performances sont essentielles. En JavaScript:

function weightedRand(spec) {
  var i, j, table=[];
  for (i in spec) {
    // The constant 10 below should be computed based on the
    // weights in the spec for a correct and optimal table size.
    // E.g. the spec {0:0.999, 1:0.001} will break this impl.
    for (j=0; j<spec[i]*10; j++) {
      table.Push(i);
    }
  }
  return function() {
    return table[Math.floor(Math.random() * table.length)];
  }
}
var Rand012 = weightedRand({0:0.8, 1:0.1, 2:0.1});
Rand012(); // random in distribution...

Une autre stratégie consiste à choisir un nombre aléatoire dans [0,1) et à effectuer une itération sur la spécification de poids en faisant la somme des poids. Si le nombre aléatoire est inférieur à la somme, renvoyez la valeur associée. Bien entendu, cela suppose que le poids total soit égal à un. Cette solution ne génère aucun coût initial, mais présente une performance algorithmique moyenne linéaire en fonction du nombre d'entrées dans la spécification. Par exemple, en JavaScript:

function weightedRand2(spec) {
  var i, sum=0, r=Math.random();
  for (i in spec) {
    sum += spec[i];
    if (r <= sum) return i;
  }
}
weightedRand2({0:0.8, 1:0.1, 2:0.1}); // random in distribution...
57
maerics

Générez un nombre aléatoire R compris entre 0 et 1.

Si R dans [0, 0.1) -> 1

Si R dans [0.1, 0.2) -> 2

Si R dans [0.2, 1] -> 3

Si vous ne pouvez pas obtenir directement un nombre compris entre 0 et 1, générez un nombre dans une plage qui produira autant de précision que vous le souhaitez. Par exemple, si vous avez les poids pour

(1, 83,7%) et (2, 16,3%), lancez un nombre allant de 1 à 1000. 1-837 est un 1. 838-1000 est 2.

18
Thomas Eding

Ceci est plus ou moins une version générique de ce que @trinithis a écrit en Java: je l'ai fait avec ints plutôt qu'avec float pour éviter les erreurs d'arrondis désordonnées.

static class Weighting {

    int value;
    int weighting;

    public Weighting(int v, int w) {
        this.value = v;
        this.weighting = w;
    }

}

public static int weightedRandom(List<Weighting> weightingOptions) {

    //determine sum of all weightings
    int total = 0;
    for (Weighting w : weightingOptions) {
        total += w.weighting;
    }

    //select a random value between 0 and our total
    int random = new Random().nextInt(total);

    //loop thru our weightings until we arrive at the correct one
    int current = 0;
    for (Weighting w : weightingOptions) {
        current += w.weighting;
        if (random < current)
            return w.value;
    }

    //shouldn't happen.
    return -1;
}

public static void main(String[] args) {

    List<Weighting> weightings = new ArrayList<Weighting>();
    weightings.add(new Weighting(0, 8));
    weightings.add(new Weighting(1, 1));
    weightings.add(new Weighting(2, 1));

    for (int i = 0; i < 100; i++) {
        System.out.println(weightedRandom(weightings));
    }
}
10
Greg Case

Voici 3 solutions en javascript, car je ne suis pas sûr de la langue dans laquelle vous le souhaitez. En fonction de vos besoins, l’une des deux premières solutions peut fonctionner, mais la troisième est probablement la plus facile à implémenter avec de grands nombres.

function randomSimple(){
  return [0,0,0,0,0,0,0,0,1,2][Math.floor(Math.random()*10)];
}

function randomCase(){
  var n=Math.floor(Math.random()*100)
  switch(n){
    case n<80:
      return 0;
    case n<90:
      return 1;
    case n<100:
      return 2;
  }
}

function randomLoop(weight,num){
  var n=Math.floor(Math.random()*100),amt=0;
  for(var i=0;i<weight.length;i++){
    //amt+=weight[i]; *alternative method
    //if(n<amt){
    if(n<weight[i]){
      return num[i];
    }
  }
}

weight=[80,90,100];
//weight=[80,10,10]; *alternative method
num=[0,1,2]
8
qw3n

Que diriez-vous

int [] nombres = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2};

alors vous pouvez choisir au hasard parmi des nombres et 0 aura une chance de 80%, 1 10% et 2 10%

6
emory

J'utilise le suivant

function weightedRandom(min, max) {
  return Math.round(max / (Math.random() * max + min));
}

Ceci est mon choix aléatoire "pondéré", où j'utilise une fonction inverse de "x" (où x est un aléatoire entre min et max) pour générer un résultat pondéré, où le minimum est l'élément le plus lourd et le maximum. le plus léger (moins de chances d'obtenir le résultat)

Donc, fondamentalement, utiliser weightedRandom(1, 5) signifie que les chances d'obtenir un 1 sont supérieures à un 2 qui sont supérieures à un 3, qui sont supérieures à un 4, qui sont supérieures à un 5.

Cela pourrait ne pas être utile pour votre cas d'utilisation mais probablement utile pour les personnes cherchant sur Google cette même question.

Après 100 essais, ça m'a donné:

==================
| Result | Times |
==================
|      1 |    55 |
|      2 |    28 |
|      3 |     8 |
|      4 |     7 |
|      5 |     2 |
==================
5
Tom Roggero

voici l'entrée et les ratios: 0 (80%), 1 (10%), 2 (10%)

permet de les dessiner afin qu’il soit facile à visualiser. 

                0                       1        2
-------------------------------------________+++++++++

ajoutons le poids total et appelons-le TR pour le rapport total. Ainsi, dans ce cas, 100. permet d’obtenir aléatoirement un nombre compris entre (0-TR) ou (0 à 100 dans ce cas). 100 étant votre poids total. Appelez-le RN pour un nombre aléatoire.

nous avons donc maintenant TR comme poids total et RN comme nombre aléatoire compris entre 0 et TR.

alors imaginons que nous ayons choisi un nombre aléatoire de 0 à 100. Disons 21. Donc, en fait, c'est 21%.

WE DOIT CONVERTIR/CORRIGER CELUI-CI AVEC NOS NUMÉROS D'ENTRÉE MAIS COMMENT?

permet de boucler chaque poids (80, 10, 10) et de garder la somme des poids que nous avons déjà visités . au moment où la somme des poids que nous bouclons est supérieure au nombre aléatoire RN (21 dans ce cas) , nous arrêtons la boucle et retournons la position de cet élément.

double sum = 0;
int position = -1;
for(double weight : weight){
position ++;
sum = sum + weight;
if(sum > 21) //(80 > 21) so break on first pass
break;
}
//position will be 0 so we return array[0]--> 0

disons que le nombre aléatoire (entre 0 et 100) est 83. Essayons de le refaire:

double sum = 0;
int position = -1;
for(double weight : weight){
position ++;
sum = sum + weight;
if(sum > 83) //(90 > 83) so break
break;
}

//we did two passes in the loop so position is 1 so we return array[1]---> 1
1
j2emanue

Celui-ci est dans Mathematica, mais il est facile de copier dans une autre langue, je l'utilise dans mes jeux et il peut gérer les poids décimaux:

weights = {0.5,1,2}; // The weights
weights = N@weights/Total@weights // Normalize weights so that the list's sum is always 1.
min = 0; // First min value should be 0
max = weights[[1]]; // First max value should be the first element of the newly created weights list. Note that in Mathematica the first element has index of 1, not 0.
random = RandomReal[]; // Generate a random float from 0 to 1;
For[i = 1, i <= Length@weights, i++,
    If[random >= min && random < max,
        Print["Chosen index number: " <> ToString@i]
    ];
    min += weights[[i]];
    If[i == Length@weights,
        max = 1,
        max += weights[[i + 1]]
    ]
]

(Maintenant, je parle avec un index de premier élément de liste égal à 0) L'idée derrière ceci est que d'avoir une liste normalisée poids il y a une chance de poids [n] pour retourner index n , donc les distances entre le minimum et le maximum au pas n devraient être de poids [n] . La distance totale entre le minimum min (ce qui correspond à 0) et le maximum max est la somme de la liste poids .

La bonne chose à faire est que vous n’ajoutez aucun tableau ni nid de boucles, ce qui augmente considérablement le temps d’exécution.

Voici le code en C # sans avoir besoin de normaliser les poids et de supprimer du code:

int WeightedRandom(List<float> weights) {
    float total = 0f;
    foreach (float weight in weights) {
        total += weight;
    }

    float max = weights [0],
    random = Random.Range(0f, total);

    for (int index = 0; index < weights.Count; index++) {
        if (random < max) {
            return index;
        } else if (index == weights.Count - 1) {
            return weights.Count-1;
        }
        max += weights[index+1];
    }
    return -1;
}
1
Garmekain

J'ai une machine à sous et j'ai utilisé le code ci-dessous pour générer des nombres aléatoires. Dans probabilitiesSlotMachine, les clés sont la sortie de la machine à sous, et les valeurs représentent le poids.

const probabilitiesSlotMachine         = [{0 : 1000}, {1 : 100}, {2 : 50}, {3 : 30}, {4 : 20}, {5 : 10}, {6 : 5}, {7 : 4}, {8 : 2}, {9 : 1}]
var allSlotMachineResults              = []

probabilitiesSlotMachine.forEach(function(obj, index){
    for (var key in obj){
        for (var loop = 0; loop < obj[key]; loop ++){
            allSlotMachineResults.Push(key)
        }
    }
});

Maintenant, pour générer une sortie aléatoire, j'utilise ce code:

const random = allSlotMachineResults[Math.floor(Math.random() * allSlotMachineResults.length)]
0
J. Doe

Je suggère d'utiliser une vérification continue de la probabilité et du reste du nombre aléatoire.

Cette fonction définit d'abord la valeur de retour sur le dernier indice possible et itère jusqu'à ce que le reste de la valeur aléatoire soit inférieur à la probabilité réelle.

Les probabilités doivent totaliser un.

function getRandomIndexByProbability(probabilities) {
    var r = Math.random(),
        index = probabilities.length - 1;

    probabilities.some(function (probability, i) {
        if (r < probability) {
            index = i;
            return true;
        }
        r -= probability;
    });
    return index;
}

var i,
    probabilities = [0.8, 0.1, 0.1],
    count = probabilities.map(function () { return 0; });

for (i = 0; i < 1e6; i++) {
    count[getRandomIndexByProbability(probabilities)]++;
}

console.log(count);
.as-console-wrapper { max-height: 100% !important; top: 0; }

0
Nina Scholz