Obtenez un nombre aléatoire centré sur le centre


238

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

En ce moment, j'utilise simplement la base Math.random() * 100 + 1.





20
J'aime où va cette question, mais je pense qu'elle devrait être plus précise. Voulez-vous une distribution Z (courbe en cloche), une distribution triangulaire ou une sorte de distribution en dents de scie? À mon avis, il existe plusieurs possibilités pour répondre à cette question.
Patrick Roberts

12
Cela peut être fait en javascript mais cela n'a certainement rien à voir avec jQuery ... :)
A. Wolff

Réponses:


397

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 biaisée vers 50, de la même manière en roulant deux biais de dés vers 7.

En fait, en utilisant un plus grand nombre de "dés" (comme le suggère @Falco) , vous pouvez faire une approximation plus proche 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;
}

Nombres aléatoires pondérés

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


12
C'est une solution simple et rapide, qui peut être facilement pondérée davantage, en ajoutant plus de nombres, par exemple 4 x (0-25) et vous donnera une belle courbe en cloche pour la distribution!
Falco

8
C'est un morceau fantastique de code. Je pense que j'aime ça. Simple, rapide, efficace; très bonne réponse. Merci d'avoir posté ceci.
ctwheels

14
Excellente réponse, mais si quelqu'un a l'intention de l'utiliser pour générer une distribution normale, elle est assez inefficace (et vous devez la transformer pour obtenir la moyenne et l'écart-type souhaités). Une option plus efficace serait la transformation Box-Muller, qui est assez facile à implémenter et à comprendre si vous connaissez un peu les mathématiques.
Brendon

1
@RaziShaban C'est assez intuitif: il n'y a qu'une seule combinaison de lancers de dés qui ajoute jusqu'à 2 (juste des yeux de serpent), mais il y a 6 combinaisons différentes qui totalisent 7 (6-1, 5-2, 4-3, 3- 4, 2-5, 1-6). Si vous généralisez aux dés à N faces, le pic est toujours N + 1.
Barmar

2
@RaziShaban L'étude des variables aléatoires est un élément central des statistiques. Le fait que lorsque nous augmentons les dés nous approchons d'une distribution normale est le célèbre théorème de la limite centrale .
BlueRaja - Danny Pflughoeft

48

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

  • J'ai une source de nombres aléatoires plus ou moins uniformément répartis 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 à déterminer la fonction quantile de la 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 fonction de distribution souhaitée . 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 au hasard soit 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 qu'il contient est en C #, mais les principes s'appliquent à n'importe quel langage; il devrait être simple d'adapter la solution à JavaScript.


2
J'aime cette approche. Pourrait ajouter qu'il existe une bibliothèque javascript qui génère des distributions gaussiennes (et autres distributions non normales): simjs.com/random.html
Floris

36

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

Distribution


2
Nous ne savons pas vraiment quelle distribution est nécessaire. "Principalement 40-60" implique pour moi une courbe en cloche.
Lefty

oui vous avez raison, vous avez peut-être besoin d'une meilleure cartographie, mais c'est trivial
iCaramba

3
Je vais vous croire sur parole parce que ce n'est pas mon domaine d'expertise. Pourriez-vous ajuster la fonction et afficher la nouvelle courbe?
Lefty

1
@Lefty - Courbe de cloche simplifiée xentre 0 et 100 (tirée de cette question ):y = (Math.sin(2 * Math.PI * (x/100 - 1/4)) + 1) / 2
Sphinxxx

@Sphinxxx Ce n'est pas une courbe en cloche, c'est une courbe en sinistre. Une courbe en cloche ne touche jamais l'axe des x.
BlueRaja - Danny Pflughoeft

17

Je pourrais faire quelque chose comme configurer une "chance" pour que le nombre soit autorisé à "sortir des limites". Dans cet exemple, 20% de chances que le nombre soit 1-100, sinon 40-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/


5
Peut-être que quelqu'un avec plus de détails statistiques peut me corriger, et bien que cela atteigne ce que le PO recherche (j'ai donc voté), mais cela ne choisirait pas vraiment un # hors limites 20% du temps, n'est-ce pas? Dans cette solution, 20% du temps, vous auriez la possibilité de choisir un # de 1-100, qui comprend 40-60. Ne serait-ce pas (0,2 * 0,8) 16% pour choisir un # hors limites, ou est-ce que je manque quelque chose?
Josh

Non, tu as raison. C'est juste ma formulation. Je vais le corriger. Je vous remercie!
Création au niveau du bit

1
@Josh - C'est assez bien. Voici une simple preuve de ce à quoi cela ressemble jsfiddle.net/v51z8sd5 . Il montrera le pourcentage de nombres trouvés hors des limites et oscille autour de 0,16 (16%).
Travis J

15

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 bornes et les ai moyennes. Cela tire le résultat vers le centre mais laisse tout à fait possible d'atteindre les extrémités.


7
En quoi est-ce meilleur / différent de la réponse de BlueRaja? Là, il prend la somme de (2,3, ... n'importe quel nombre que vous voulez) des nombres aléatoires et prend la moyenne. Le résultat est identique au vôtre lorsque vous utilisez un BellFactorde 3.
Floris

@floris bien, je ne code pas dans la famille de langues c, donc cette réponse ne semblait même pas faire la même chose que ma réponse jusqu'à ce que je la relise maintenant. J'ai créé ma méthode par un peu d'essais et d'erreurs et j'ai trouvé que 3 randoms étaient le bon nombre. De plus, le mien peut être fait en une seule ligne et être toujours facile à comprendre.
Lefty

2
Vraiment? Vous ne pensez pas qu'il existe une similitude entre JS et C? OK, eh bien, disons simplement que je ne peux parler AUCUN de ces langages, ni Java, qui, pour moi, sont tous similaires par rapport aux langages que je connais.
Lefty

1
Bon point, je n'étais en fait attiré que par le titre comme étant quelque chose que j'avais résolu moi-même et j'étais assez fier de la façon dont je l'ai fait. Encore une fois, je ne savais pas que c'était une question js jusqu'à ce que vous veniez de le dire. Heureusement, parce que ma technique ne dépend pas de la langue et certaines personnes semblent penser que c'est une réponse utile.
Lefty

5
JavaScript est en fait un langage de la famille C ... mais ah bien.
Joren

14

Cela a l' air 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;
}

11

Bien sûr, c'est possible. Faites un 1-100 aléatoire. Si le nombre est <30, générer un nombre compris entre 1 et 100 sinon générer entre 40 et 60.


11

Il existe de nombreuses façons 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 plage détermineront à quoi ressemblera la distribution finale.

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

Vous pouvez obtenir encore plus de biais vers le centre de la plage en prenant la somme de nombres plus aléatoires. À l'extrême, vous pourriez prendre la somme de 99 nombres aléatoires qui étaient chacun 0 ou 1. Ce serait une distribution binomiale. (Les distributions binomiales peuvent dans un certain sens être considérées comme la version discrète des distributions normales). Cela peut toujours en théorie couvrir toute la gamme, mais il a tellement de biais vers le centre que vous ne devriez jamais vous attendre à le voir atteindre les points d'extrémité.

Cette approche signifie que vous pouvez modifier le niveau de biais souhaité.


8

Qu'en est-il de l'utilisation de 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 je l'ai codé vous permet de définir quelques variables:
boucles = nombre d' essais de résultats
= nombre de fois où la fonction essaiera d'obtenir un nombre compris entre 40 et 60 avant d'arrêter de parcourir la boucle while

Bonus supplémentaire: il utilise do while !!! L'émerveillement à son meilleur


8

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

0,0-1,0 à 1-100 en pourcentage en poids

Ici, la valeur 0.95correspond à une valeur entre [61, 100].
En fait, nous avons .05 / .1 = 0.5, ce qui, une fois mappé à [61, 100], donne81 .

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>


7

Voici une solution pondérée à 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>


6

Ok, j'ai donc décidé d'ajouter une autre réponse parce que je me sentais comme ma dernière réponse, ainsi que la plupart des réponses ici, utilisent une sorte de méthode semi-statistique pour obtenir un retour de résultat de type 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 plus difficile d'obtenir 1 ou 99, mais plus facile d'en 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

Je recommanderais d'utiliser la distribution bêta pour générer un nombre compris entre 0 et 1, puis l'agrandir. Il est assez flexible et peut créer de nombreuses formes de distribution différentes.

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))
}

5
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

La meilleure solution ciblant 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 mérite également d'être mentionnée.


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

  1. L'ensemble de résultats est assez petit. (pas plus de 16K nombres)
  2. L'ensemble de résultats est discret. (comme les nombres entiers uniquement)

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 les plus probables plusieurs fois.), Puis choisissez un élément aléatoire de ce tableau. De cette façon, vous n'avez qu'à appeler la fonction aléatoire coûteuse une fois par article.


1
Si vous allez préremplir un éventail de choix, vous pouvez également les mélanger par la suite. Ensuite, vous pouvez simplement les saisir dans l'ordre jusqu'à épuisement. Mélangez à nouveau si / quand vous atteignez la fin de la liste.
Geobits

@Geobits Mélanger une liste est une tâche beaucoup plus gourmande en ressources que de choisir au hasard l'un de ses éléments. Ce n'est un bon choix que si la liste doit être prévisible.
mg30rg

1
Mais vous ne le faites qu'une fois par cycle de la liste au lieu de chaque fois. Si vous prétraitez cela (puisque vous avez quand même une étape de prétraitement, je suppose que c'est bien), alors il est très rapide d'obtenir chaque numéro par la suite. Vous pouvez remanier chaque fois que vous avez un temps d'arrêt, ou savoir que vous n'aurez pas besoin d'un nombre aléatoire pour un peu. L'offrir simplement comme une alternative, les deux ont des (dés) avantages.
Geobits

@Geobits Si vous le faites à votre façon, les nombres "à probabilité unique" "tomberont" et jusqu'à ce qu'ils soient réorganisés, ils ne peuvent pas apparaître en conséquence. (c.-à-d. si vous simulez le lancement de deux dés, vous n'aurez pas la moindre chance d'obtenir le numéro 2 plus de deux fois.)
mg30rg

1
C'est une bien meilleure raison de ne pas l'utiliser, sauf pour les rares applications où ça va;)
Geobits

4

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

Vous pouvez utiliser un nombre aléatoire d'aide pour générer des nombres aléatoires en 40-60 ou 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

Si vous pouvez utiliser la gaussianfonction, utilisez-la. Cette fonction renvoie un nombre normal avecaverage 0 et sigma 1.

95% de ce nombre sont à l'intérieur average +/- 2*sigma. Votre average = 50, et sigma = 5ainsi

randomNumber = 50 + 5*gaussian()

3

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

En règle générale, la manière mathématique d'y parvenir consiste à tracer une fonction de probabilité des nombres que vous souhaitez. Nous pourrions utiliser la courbe en cloche, mais pour un calcul plus facile, travaillons simplement avec une parabole retournée.

Faisons une parabole telle que ses racines soient à 0 et 100 sans l'incliner. 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 devons faire est de trouver les limites de notre premier ensemble.

La borne inférieure est, bien sûr, 0. La borne 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 est compris entre 0 et 100.

Nous savons que le nombre aléatoire que nous avons généré est une intégrale de notre parabole avec une entrée x 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 pour 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)

La résolution de ceci pour x vous donne le nombre aléatoire réel que vous recherchez, ce qui est garanti d'être dans la plage [0, 100] et une probabilité beaucoup plus élevée d'être proche du centre que des bords.


3

Cette réponse est vraiment bonne . Mais je voudrais publier des instructions d'implémentation (je ne suis pas en JavaScript, donc j'espère que vous comprendrez) pour différentes situations.


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

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

Les informations statiques initiales peuvent être mises en cache:

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

Génération de nombres:

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

Remarque supplémentaire pour les optimisations de performances. Les plages ne doivent être ordonnées ni dans l'ordre croissant ou décroissant, donc pour une plage de recherche de plage plus rapide qui a le poids le plus élevé doit aller en premier et une avec le poids le plus bas doit aller en dernier.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.