web-dev-qa-db-fra.com

Obtenir un nombre aléatoire axé sur le centre

Est-il possible d'obtenir un nombre aléatoire compris entre 1 et 100 et de conserver les résultats principalement dans la plage des 40 à 60? Je veux dire, il sortira rarement de cette plage, mais je veux que ce soit principalement dans cette plage ... Est-ce possible avec JavaScript/jQuery?

En ce moment, je n'utilise que la base Math.random() * 100 + 1.

237
Darryl Huffman

Le moyen le plus simple serait de générer deux nombres aléatoires de 0 à 50 et de les additionner.

Cela donne une distribution orientée vers 50, de la même manière en lançant deux dés de biais vers 7.

En fait, en utilisant un plus grand nombre de "dés" (comme le suggère @Falco) , vous pouvez vous rapprocher d'une courbe en cloche:

function weightedRandom(max, numDice) {
    var num = 0;
    for (var i = 0; i < numDice; i++) {
        num += Math.random() * (max/numDice);
    }    
    return num;
}

Weighted random numbers

JSFiddle: http://jsfiddle.net/797qhcza/1/

Vous avez de bonnes réponses ici qui donnent des solutions spécifiques; laissez-moi vous décrire la solution générale. Le problème est:

  • J'ai une source de plus ou moins uniformément nombres aléatoires distribués entre 0 et 1.
  • Je souhaite produire une séquence de nombres aléatoires qui suivent une distribution différente.

La solution générale à ce problème consiste à élaborer la fonction quantile de votre distribution souhaitée, puis à appliquer la fonction quantile à la sortie de votre source uniforme.

La fonction quantile est l’inverse de l’intégrale de votre désirée fonction de distribution . La fonction de distribution est la fonction où l'aire sous une partie de la courbe est égale à la probabilité que l'élément choisi de manière aléatoire se trouve dans cette partie.

Je donne un exemple de la façon de le faire ici:

http://ericlippert.com/2012/02/21/generating-random-non-uniform-data/

Le code y est en C #, mais les principes s’appliquent à toutes les langues; il devrait être simple d'adapter la solution à JavaScript.

48
Eric Lippert

Prendre des tableaux de chiffres, etc. n'est pas efficace. Vous devez effectuer un mappage qui prend un nombre aléatoire compris entre 0 et 100 et mapper sur la distribution dont vous avez besoin. Donc, dans votre cas, vous pourriez utiliser f(x)=-(1/25)x2+4x pour obtenir une distribution avec le plus grand nombre de valeurs au milieu de votre plage.

Distribution

36
iCaramba

Je pourrais faire quelque chose comme configurer une "chance" pour que le nombre soit autorisé à "sortir des limites". Dans cet exemple, une chance sur 20 que le nombre soit compris entre 1 et 100, sinon, entre 40 et 60:

$(function () {
    $('button').click(function () {
        var outOfBoundsChance = .2;
        var num = 0;
        if (Math.random() <= outOfBoundsChance) {
            num = getRandomInt(1, 100);
        } else {
            num = getRandomInt(40, 60);
        }
        $('#out').text(num);
    });
    
    function getRandomInt(min, max) {
        return Math.floor(Math.random() * (max - min + 1)) + min;
    }
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<button>Generate</button>
<div id="out"></div>

violon: http://jsfiddle.net/kbv39s9w/

17
Bitwise Creative

J'avais besoin de résoudre ce problème il y a quelques années et ma solution était plus facile que toutes les autres réponses.

J'ai généré 3 aléas entre les limites et leur moyenne. Cela tire le résultat vers le centre mais laisse tout à fait possible d'atteindre les extrémités.

15
Lefty

C'est semble stupide mais vous pouvez utiliser Rand deux fois:

var choice = Math.random() * 3;
var result;

if (choice < 2){
    result = Math.random() * 20 + 40; //you have 2/3 chance to go there
}
else {
    result = Math.random() * 100 + 1;
}
14
max890

Bien sûr que c'est possible. Faites un aléatoire 1-100. Si le nombre est <30, générez le nombre dans la plage 1-100, sinon dans la plage 40-60.

11
Luka Krajnc

Il y a beaucoup de façons différentes de générer de tels nombres aléatoires. Une façon de le faire est de calculer la somme de plusieurs nombres uniformément aléatoires. Le nombre de nombres aléatoires que vous additionnez et leur étendue détermineront l’allure de la distribution finale.

Plus vous additionnez les chiffres, plus il sera biaisé vers le centre. L'utilisation de la somme de 1 nombre aléatoire a déjà été proposée dans votre question, mais comme vous le remarquerez, elle n'est pas biaisée vers le centre de la plage. D'autres réponses ont proposé d'utiliser le somme de 2 nombres aléatoires ou le somme de 3 nombres aléatoires .

Vous pouvez obtenir encore plus de biais vers le centre de la plage en prenant la somme de plus de nombres aléatoires. À la limite, vous pourriez prendre la somme de 99 nombres aléatoires, chacun étant 0 ou 1. Ce serait une distribution binomiale. (Les distributions binomiales peuvent en quelque sorte être considérées comme la version discrète des distributions normales). Cela peut encore théoriquement couvrir toute la gamme, mais il a tellement de biais en faveur du centre que vous ne devriez jamais vous attendre à le voir atteindre les points de terminaison.

Cette approche signifie que vous pouvez modifier exactement le degré de partialité que vous souhaitez.

11
kasperd

Qu'en est-il d'utiliser quelque chose comme ceci:

var loops = 10;
var tries = 10;
var div = $("#results").html(random());
function random() {
    var values = "";
    for(var i=0; i < loops; i++) {
        var numTries = tries;
        do {
            var num = Math.floor((Math.random() * 100) + 1);
            numTries--;
        }
        while((num < 40 || num >60) && numTries > 1)
        values += num + "<br/>";
    }
    return values;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="results"></div>

La façon dont j'ai codé cela vous permet de définir quelques variables:
boucles = nombre de résultats
essaye = nombre de fois que la fonction tentera d'obtenir un nombre entre 40 et 60 avant d'arrêter de passer dans la boucle while

Bonus supplémentaire: Il utilise do while !!! Awesomeness à son meilleur

8
ctwheels

Vous pouvez écrire une fonction qui mappe des valeurs aléatoires entre [0, 1) à [1, 100] en fonction du poids. Considérons cet exemple:

0.0-1.0 to 1-100 by percentage weight

Ici, la valeur 0.95 correspond à la valeur entre [61, 100].
En fait, nous avons .05 / .1 = 0.5, qui, lorsqu'il est associé à [61, 100], donne 81.

Voici la fonction:

/*
 * Function that returns a function that maps random number to value according to map of probability
 */
function createDistributionFunction(data) {
  // cache data + some pre-calculations
  var cache = [];
  var i;
  for (i = 0; i < data.length; i++) {
    cache[i] = {};
    cache[i].valueMin = data[i].values[0];
    cache[i].valueMax = data[i].values[1];
    cache[i].rangeMin = i === 0 ? 0 : cache[i - 1].rangeMax;
    cache[i].rangeMax = cache[i].rangeMin + data[i].weight;
  }
  return function(random) {
    var value;
    for (i = 0; i < cache.length; i++) {
      // this maps random number to the bracket and the value inside that bracket
      if (cache[i].rangeMin <= random && random < cache[i].rangeMax) {
        value = (random - cache[i].rangeMin) / (cache[i].rangeMax - cache[i].rangeMin);
        value *= cache[i].valueMax - cache[i].valueMin + 1;
        value += cache[i].valueMin;
        return Math.floor(value);
      }
    }
  };
}

/*
 * Example usage
 */
var distributionFunction = createDistributionFunction([
  { weight: 0.1, values: [1, 40] },
  { weight: 0.8, values: [41, 60] },
  { weight: 0.1, values: [61, 100] }
]);

/*
 * Test the example and draw results using Google charts API
 */
function testAndDrawResult() {
  var counts = [];
  var i;
  var value;
  // run the function in a loop and count the number of occurrences of each value
  for (i = 0; i < 10000; i++) {
    value = distributionFunction(Math.random());
    counts[value] = (counts[value] || 0) + 1;
  }
  // convert results to datatable and display
  var data = new google.visualization.DataTable();
  data.addColumn("number", "Value");
  data.addColumn("number", "Count");
  for (value = 0; value < counts.length; value++) {
    if (counts[value] !== undefined) {
      data.addRow([value, counts[value]]);
    }
  }
  var chart = new google.visualization.ColumnChart(document.getElementById("chart"));
  chart.draw(data);
}
google.load("visualization", "1", { packages: ["corechart"] });
google.setOnLoadCallback(testAndDrawResult);
<script src="https://www.google.com/jsapi"></script>
<div id="chart"></div>
8
Salman A

Voici une solution pondérée aux 3/4 40-60 et 1/4 en dehors de cette plage.

function weighted() {

  var w = 4;

  // number 1 to w
  var r = Math.floor(Math.random() * w) + 1;

  if (r === 1) { // 1/w goes to outside 40-60
    var n = Math.floor(Math.random() * 80) + 1;
    if (n >= 40 && n <= 60) n += 40;
    return n
  }
  // w-1/w goes to 40-60 range.
  return Math.floor(Math.random() * 21) + 40;
}

function test() {
  var counts = [];

  for (var i = 0; i < 2000; i++) {
    var n = weighted();
    if (!counts[n]) counts[n] = 0;
    counts[n] ++;
  }
  var output = document.getElementById('output');
  var o = "";
  for (var i = 1; i <= 100; i++) {
    o += i + " - " + (counts[i] | 0) + "\n";
  }
  output.innerHTML = o;
}

test();
<pre id="output"></pre>
7
wolfhammer

Je recommanderais d'utiliser distribution bêta pour générer un nombre compris entre 0 et 1, puis le redimensionner. C'est assez flexible et peut créer beaucoup de formes différentes de distributions.

Voici un échantillonneur rapide et sale:

rbeta = function(alpha, beta) {
 var a = 0   
 for(var i = 0; i < alpha; i++)   
    a -= Math.log(Math.random())

 var b = 0   
 for(var i = 0; i < beta; i++)   
    b -= Math.log(Math.random())

  return Math.ceil(100 * a / (a+b))
}
6
Neal Fultz

Ok, j’ai donc décidé d’ajouter une autre réponse car j’avais l’impression que ma dernière réponse, ainsi que la plupart des réponses ici, utilisait une méthode semi-statistique d’obtention d’un résultat obtenu sous forme de courbe en cloche. Le code que je fournis ci-dessous fonctionne de la même manière que lorsque vous lancez un dé. Par conséquent, il est le plus difficile d’obtenir 1 ou 99, mais le plus facile d’obtenir 50.

var loops = 10; //Number of numbers generated
var min = 1,
    max = 50;
var div = $("#results").html(random());

function random() {
    var values = "";
    for (var i = 0; i < loops; i++) {
        var one = generate();
        var two = generate();
        var ans = one + two - 1;
        var num = values += ans + "<br/>";
    }
    return values;
}

function generate() {
    return Math.floor((Math.random() * (max - min + 1)) + min);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="results"></div>
6
ctwheels
var randNum;
// generate random number from 1-5
var freq = Math.floor(Math.random() * (6 - 1) + 1);
// focus on 40-60 if the number is odd (1,3, or 5)
// this should happen %60 of the time
if (freq % 2){
    randNum = Math.floor(Math.random() * (60 - 40) + 40);
}
else {
    randNum = Math.floor(Math.random() * (100 - 1) + 1);
}
5
Olaide Oyekoya

La meilleure solution ciblant précisément ce problème est celle proposée par BlueRaja - Danny Pflughoeft mais je pense qu’une solution un peu plus rapide et plus générale vaut également la peine d’être mentionnée.


Lorsque je dois générer des nombres aléatoires (chaînes, paires de coordonnées, etc.) satisfaisant les deux exigences de

  1. Le jeu de résultats est assez petit. (pas plus grand que 16K nombres)
  2. Le résultat est discret. (comme les nombres entiers seulement)

Je commence généralement par créer un tableau de nombres (chaînes, paires de coordonnées, etc.) répondant à l'exigence (dans votre cas: un tableau de nombres contenant plusieurs fois les probabilités.), Puis choisissez un élément aléatoire de ce tableau. De cette façon, il vous suffit d'appeler la fonction aléatoire coûteuse une fois par élément.

5
mg30rg

Distribution

 5% for [ 0,39]
90% for [40,59]
 5% for [60,99]

Solution

var f = Math.random();
if (f < 0.05) return random(0,39);
else if (f < 0.95) return random(40,59);
else return random(60,99);

Solution générique

random_choose([series(0,39),series(40,59),series(60,99)],[0.05,0.90,0.05]);

function random_choose (collections,probabilities)
{
    var acc = 0.00;
    var r1 = Math.random();
    var r2 = Math.random();

    for (var i = 0; i < probabilities.length; i++)
    {
      acc += probabilities[i];
      if (r1 < acc)
        return collections[i][Math.floor(r2*collections[i].length)];
    }

    return (-1);
}

function series(min,max)
{
    var i = min; var s = [];
    while (s[s.length-1] < max) s[s.length]=i++;
    return s;
}
4
Khaled.K

Vous pouvez utiliser un nombre aléatoire auxiliaire pour générer des nombres aléatoires de 40 à 60 ou de 1 à 100:

// 90% of random numbers should be between 40 to 60.
var weight_percentage = 90;

var focuse_on_center = ( (Math.random() * 100) < weight_percentage );

if(focuse_on_center)
{
        // generate a random number within the 40-60 range.
        alert (40 + Math.random() * 20 + 1);
}
else
{
        // generate a random number within the 1-100 range.
        alert (Math.random() * 100 + 1);
}
4
Amir Saniyan

Si vous pouvez utiliser la fonction gaussian, utilisez-la. Cette fonction retourne un nombre normal avec average 0 et sigma 1.

95% de ce nombre sont dans les average +/- 2*sigma. Votre average = 50, et sigma = 5 alors

randomNumber = 50 + 5*gaussian()

Cette réponse est vraiment bonne . Mais j'aimerais publier des instructions de mise en œuvre (je ne suis pas en JavaScript, j'espère donc que vous comprendrez) dans des situations différentes.


Supposons que vous avez des plages et des poids pour chaque plage:

ranges - [1, 20], [21, 40], [41, 60], [61, 100]
weights - {1, 2, 100, 5}

Informations statiques initiales, peuvent être mises en cache:

  1. Somme de tous les poids (108 dans l'échantillon)
  2. Limites de sélection de la plage. C'est essentiellement cette formule: Boundary[n] = Boundary[n - 1] + weigh[n - 1] Et Boundary[0] = 0. L'échantillon a Boundary = {0, 1, 3, 103, 108}

Génération de nombre:

  1. Génère un nombre aléatoire N à partir de la plage [0, somme de tous les poids).
  2. for (i = 0; i < size(Boundary) && N > Boundary[i + 1]; ++i)
  3. Prenez ième plage et générez un nombre aléatoire dans cette plage.

Note complémentaire pour l'optimisation des performances. Les gammes ne doivent pas nécessairement être ordonnées en ordre croissant ou décroissant. Par conséquent, pour une plage plus rapide, la plage avec le poids le plus élevé doit aller en premier et celle avec le poids le plus bas en dernier.

3
ST3

La meilleure façon de le faire est de générer un nombre aléatoire réparti de manière égale dans un certain ensemble de nombres, puis d'appliquer une fonction de projection à l'ensemble compris entre 0 et 100, où la projection a plus de chances d'atteindre les nombres souhaités.

Généralement, la méthode mathématique pour y parvenir consiste à tracer une fonction de probabilité des nombres souhaités. Nous pourrions utiliser la courbe en cloche, mais pour faciliter les calculs, travaillons avec une parabole inversée.

Faisons une parabole telle que ses racines soient à 0 et 100 sans la biaiser. Nous obtenons l'équation suivante:

f(x) = -(x-0)(x-100) = -x * (x-100) = -x^2 + 100x

Maintenant, toute l’aire sous la courbe entre 0 et 100 est représentative de notre premier ensemble où nous voulons que les nombres soient générés. Là, la génération est complètement aléatoire. Donc, tout ce que nous avons à faire est de trouver les limites de notre premier ensemble.

La limite inférieure est, bien sûr, 0. La limite supérieure est l'intégrale de notre fonction à 100, qui est

F(x) = -x^3/3 + 50x^2
F(100) = 500,000/3 = 166,666.66666 (let's just use 166,666, because rounding up would make the target out of bounds)

Nous savons donc que nous devons générer un nombre compris entre 0 et 166 666. Ensuite, nous devons simplement prendre ce nombre et le projeter dans notre deuxième ensemble, qui se situe entre 0 et 100.

Nous savons que le nombre aléatoire que nous avons généré est une partie intégrante de notre parabole avec une entrée x comprise entre 0 et 100. Cela signifie que nous devons simplement supposer que le nombre aléatoire est le résultat de F (x) et résoudre x.

Dans ce cas, F(x) est une équation cubique et, sous la forme F(x) = ax^3 + bx^2 + cx + d = 0, les affirmations suivantes sont vraies:

a = -1/3
b = 50
c = 0
d = -1 * (your random number)

En résolvant ceci pour x, vous obtenez le nombre aléatoire réel que vous recherchez, qui est garanti pour être dans la plage [0, 100] et une probabilité beaucoup plus élevée d'être près du centre que les bords.

3
arik