Obtenga un número aleatorio enfocado en el centro


238

¿Es posible obtener un número aleatorio entre 1-100 y mantener los resultados principalmente dentro del rango 40-60? Quiero decir, rara vez saldrá de ese rango, pero quiero que esté principalmente dentro de ese rango ... ¿Es posible con JavaScript / jQuery?

Ahora mismo solo estoy usando lo básico Math.random() * 100 + 1.





20
Me gusta a dónde va esta pregunta, pero creo que debería ser más específica. ¿Desea una distribución Z (curva de campana), una distribución triangular o algún tipo de distribución de diente de sierra? Hay múltiples posibilidades para responder esta pregunta en mi opinión.
Patrick Roberts

12
Esto se puede hacer en javascript pero seguro no tiene nada que ver con jQuery ... :)
A. Wolff

Respuestas:


397

La forma más sencilla sería generar dos números aleatorios del 0 al 50 y sumarlos.

Esto da una distribución sesgada hacia 50, de la misma manera tirando dos sesgos de dados hacia 7.

De hecho, al usar un mayor número de "dados" (como sugiere @Falco) , puede hacer una aproximación más cercana a una curva de campana:

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

Números aleatorios ponderados

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


12
Esta es una solución fácil y rápida, que se puede ponderar fácilmente más, agregando más números, por ejemplo, 4 x (0-25) y le dará una bonita curva de campana para la distribución.
Falco

8
Este es un código fantástico. Creo que estoy enamorado de eso. Simple, rápido, eficiente; gran respuesta. Gracias por publicar esto.
ctwheels

14
Gran respuesta, pero en caso de que alguien intente usar esto para generar una distribución Normal, es bastante ineficiente (y necesita transformarlo para obtener la media y la desviación estándar deseadas). Una opción más eficiente sería la transformación Box-Muller, que es bastante fácil de implementar y comprender si conoce un poco de matemática.
Brendon

1
@RaziShaban Es bastante intuitivo: solo hay una combinación de tiradas que suma 2 (solo ojos de serpiente), pero hay 6 combinaciones diferentes que suman 7 (6-1, 5-2, 4-3, 3- 4, 2-5, 1-6). Si generaliza a dados de N lados, el pico siempre es N + 1.
Barmar

2
@RaziShaban El estudio de variables aleatorias es una parte central de las estadísticas. El hecho de que a medida que aumentamos los dados nos acercamos a una distribución normal es el famoso Teorema del límite central .
BlueRaja - Danny Pflughoeft

48

Tienes algunas buenas respuestas aquí que dan soluciones específicas; déjame describirte la solución general. El problema es:

  • Tengo una fuente de números aleatorios distribuidos más o menos uniformemente entre 0 y 1.
  • Deseo producir una secuencia de números aleatorios que sigan una distribución diferente.

La solución general a este problema es resolver la función cuantil de su distribución deseada y luego aplicar la función cuantil a la salida de su fuente uniforme.

La función cuantil es la inversa de la integral de su función de distribución deseada . La función de distribución es la función donde el área debajo de una porción de la curva es igual a la probabilidad de que el elemento elegido al azar esté en esa porción.

Doy un ejemplo de cómo hacerlo aquí:

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

El código allí está en C #, pero los principios se aplican a cualquier lenguaje; debería ser sencillo adaptar la solución a JavaScript.


2
Me gusta este enfoque. Es posible que desee agregar que existe una biblioteca de JavaScript que genera distribuciones gaussianas (y otras no normales): simjs.com/random.html
Floris

36

Tomar matrices de números, etc. no es eficiente. Debe tomar una asignación que tome un número aleatorio entre 0 y 100 y asigna la distribución que necesita. Entonces, en su caso, podría tomar para obtener una distribución con la mayoría de los valores en el medio de su rango.f(x)=-(1/25)x2+4x

Distribución


2
En realidad no sabemos qué distribución se necesita. "Principalmente 40-60" implica una curva de campana para mí.
Zurdo

sí, tienes razón, tal vez necesites un mejor mapeo, pero eso es trivial
iCaramba

3
Tomaré tu palabra para eso porque esta no es mi área de especialización. ¿Podría ajustar la función y mostrar la nueva curva?
Zurdo

1
@Lefty - Curva de campana simplificada para xentre 0 y 100 (tomado de esta pregunta ):y = (Math.sin(2 * Math.PI * (x/100 - 1/4)) + 1) / 2
Sphinxxx

@Sphinxxx Eso no es una curva de campana, es una curva de pecado. Una curva de campana nunca toca el eje x.
BlueRaja - Danny Pflughoeft

17

Podría hacer algo como configurar una "oportunidad" para que el número se deje "fuera de los límites". En este ejemplo, una probabilidad del 20% de que el número sea 1-100, de lo contrario, 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>

violín: http://jsfiddle.net/kbv39s9w/


55
Tal vez alguien con más detalles estadísticos pueda corregirme, y aunque esto logra lo que el OP está buscando (así que voté), pero esto realmente no elegiría un # fuera de límites el 20% del tiempo, ¿correcto? En esta solución, el 20% de las veces tendrá la oportunidad de elegir un # de 1-100, que incluye 40-60. ¿No sería esto (0.2 * 0.8) 16% para elegir un # fuera de límites, o me estoy perdiendo algo?
Josh

No, tienes razon. Es solo mi redacción. Lo corregiré ¡Gracias!
Bitwise Creative

1
@ Josh: Eso es muy acertado. Aquí hay una prueba simple de lo que parece jsfiddle.net/v51z8sd5 . Mostrará el porcentaje de números que se encuentran fuera de los límites y oscila alrededor de 0.16 (16%).
Travis J

15

Necesitaba resolver este problema hace unos años y mi solución fue más fácil que cualquiera de las otras respuestas.

Genere 3 randoms entre los límites y los promedié. Esto empuja el resultado hacia el centro pero deja completamente posible llegar a las extremidades.


77
¿Cómo es esto mejor / diferente que la respuesta de BlueRaja? Allí, toma la suma de (2,3, ... cualquier número que desee) números aleatorios y toma el promedio. El resultado es idéntico al suyo cuando usa un BellFactorde 3.
Floris

@floris bueno, no codifico en la familia de lenguajes c, por lo que esa respuesta ni siquiera parecía estar haciendo lo mismo que mi respuesta hasta que la volví a leer ahora. Creé mi método por un poco de prueba y error y descubrí que 3 randoms era el número correcto. Además, el mío se puede hacer en una línea y aún así ser fácil de entender.
Zurdo

2
De Verdad? ¿No crees que haya alguna similitud entre JS y C? Bien, digamos que no puedo hablar CUALQUIERA de esos idiomas, ni Java, que, para mí, son todos similares en comparación con los idiomas con los que estoy familiarizado.
Zurdo

1
En realidad, solo me atrajo el título como algo que había resuelto y estaba muy orgulloso de la forma en que lo hice. Una vez más, no sabía que era una pregunta js hasta que dijiste eso. Suerte realmente, porque mi técnica no depende del idioma y algunas personas parecen pensar que es una respuesta útil.
Zurdo

55
JavaScript en realidad es un lenguaje de familia C ... pero bueno.
Joren

14

Se ve estúpida pero se puede utilizar rand dos veces:

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

Claro que es posible. Haz un aleatorio 1-100. Si el número es <30, genere un número en el rango 1-100 si no se genera en el rango 40-60.


11

Hay muchas formas diferentes de generar tales números aleatorios. Una forma de hacerlo es calcular la suma de múltiples números uniformemente aleatorios. Cuántos números aleatorios sumes y cuál es su rango determinará cómo se verá la distribución final.

Cuantos más números sumes, más sesgará hacia el centro. El uso de la suma de 1 número aleatorio ya se propuso en su pregunta, pero, como verá, no está sesgado hacia el centro del rango. Otras respuestas han propuesto usar la suma de 2 números aleatorios o la suma de 3 números aleatorios .

Puede obtener aún más sesgo hacia el centro del rango tomando la suma de más números aleatorios. En el extremo, podría tomar la suma de 99 números aleatorios, cada uno de los cuales era 0 o 1. Esa sería una distribución binomial. (Las distribuciones binomiales pueden verse en cierto sentido como la versión discreta de las distribuciones normales). En teoría, esto aún puede abarcar todo el rango, pero tiene tanta inclinación hacia el centro que nunca se debe esperar que llegue a los puntos finales.

Este enfoque significa que puede ajustar la cantidad de sesgo que desea.


8

¿Qué pasa con el uso de algo como esto:

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 forma en que lo codifiqué le permite establecer un par de variables:
bucles = número de
intentos de resultados = número de veces que la función intentará obtener un número entre 40-60 antes de que deje de ejecutarse en el bucle while

Bonificación adicional: ¡Utiliza do while! Impresionante en su mejor momento


8

Puede escribir una función que asigna valores aleatorios entre los [0, 1)que [1, 100]según el peso. Considere este ejemplo:

0.0-1.0 a 1-100 en peso porcentual

Aquí, el valor se 0.95asigna al valor entre [61, 100].
De hecho, tenemos .05 / .1 = 0.5, que, cuando se asigna a [61, 100], produce81 .

Aquí está la función:

/*
 * 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

Aquí hay una solución ponderada a 3/4 40-60 y 1/4 fuera de ese rango.

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, entonces decidí agregar otra respuesta porque sentí que mi última respuesta, así como la mayoría de las respuestas aquí, usan algún tipo de medio estadístico para obtener un retorno del resultado del tipo de curva de campana. El código que proporciono a continuación funciona de la misma manera que cuando lanzas un dado. Por lo tanto, es más difícil obtener 1 o 99, pero es más fácil obtener 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

Recomiendo usar la distribución beta para generar un número entre 0-1, luego escalarlo. Es bastante flexible y puede crear muchas formas diferentes de distribuciones.

Aquí hay una muestra rápida y sucia:

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 mejor solución para este problema es la propuesta por BlueRaja: Danny Pflughoeft, pero creo que también vale la pena mencionar una solución algo más rápida y más general.


Cuando tengo que generar números aleatorios (cadenas, pares de coordenadas, etc.) que satisfacen los dos requisitos de

  1. El conjunto de resultados es bastante pequeño. (no mayor a 16K números)
  2. El conjunto de resultados es discreto. (como solo números enteros)

Por lo general, empiezo creando una matriz de números (cadenas, pares de coordenadas, etc.) cumpliendo el requisito (en su caso: una matriz de números que contiene los más probables varias veces), luego elijo un elemento aleatorio de esa matriz. De esta manera, solo tiene que llamar a la costosa función aleatoria una vez por artículo.


1
Si va a rellenar previamente una variedad de opciones, también puede barajarlas después. Luego puedes agarrarlos en orden hasta que te quedes sin ellos. Vuelva a barajar si llega al final de la lista.
Geobits

@Geobits Mezclar una lista es una tarea que requiere muchos más recursos que elegir aleatoriamente uno de sus elementos. Es solo una buena opción si la lista tiene que ser predecible.
mg30rg

1
Pero solo lo haces una vez por ciclo de la lista en lugar de cada vez. Si preprocesas esto (dado que tienes un paso de preprocesamiento de todos modos, supongo que está bien), entonces es muy rápido obtener cada número después. Puede reorganizar cada vez que tenga tiempo de inactividad, o saber que no necesitará un número aleatorio por un momento. Solo ofreciéndolo como alternativa, ambos tienen (des) ventajas.
Geobits

@Geobits Si lo hace a su manera, los números de "probabilidad única" se "caerán" y, como resultado, no podrán aparecer de nuevo. (es decir, si simula el lanzamiento de dos dados, no tendrá la menor oportunidad de obtener el número 2 más de dos veces).
mg30rg

1
Esa es una razón mucho mejor para no usarlo, a excepción de las aplicaciones raras donde eso está bien;)
Geobits

4

Distribución

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

Solución

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

Solución Genérica

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

Puede usar un número aleatorio auxiliar para generar números aleatorios en 40-60 o 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 puede usar la gaussianfunción, úsela. Esta función devuelve un número normal con average 0y sigma 1.

El 95% de este número está dentro average +/- 2*sigma. Tu average = 50y sigma = 5así

randomNumber = 50 + 5*gaussian()

3

La mejor manera de hacerlo es generar un número aleatorio que se distribuya equitativamente en un determinado conjunto de números, y luego aplicar una función de proyección al conjunto entre 0 y 100, donde la proyección es más probable que llegue a los números que desea.

Por lo general, la forma matemática de lograr esto es trazar una función de probabilidad de los números que desea. Podríamos usar la curva de campana, pero en aras de un cálculo más fácil, simplemente trabajemos con una parábola invertida.

Hagamos una parábola de modo que sus raíces estén en 0 y 100 sin sesgarla. Obtenemos la siguiente ecuación:

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

Ahora, toda el área bajo la curva entre 0 y 100 es representativa de nuestro primer conjunto donde queremos que se generen los números. Allí, la generación es completamente al azar. Entonces, todo lo que necesitamos hacer es encontrar los límites de nuestro primer conjunto.

El límite inferior es, por supuesto, 0. El límite superior es la integral de nuestra función en 100, que es

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)

Entonces sabemos que necesitamos generar un número en algún lugar entre 0 y 166,666. Luego, simplemente necesitamos tomar ese número y proyectarlo en nuestro segundo conjunto, que está entre 0 y 100.

Sabemos que el número aleatorio que generamos es parte integral de nuestra parábola con una entrada x entre 0 y 100. Eso significa que simplemente tenemos que suponer que el número aleatorio es el resultado de F (x) y resolver para x.

En este caso, F (x) es una ecuación cúbica, y en la forma F(x) = ax^3 + bx^2 + cx + d = 0, las siguientes afirmaciones son verdaderas:

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

Resolver esto para x te da el número aleatorio real que estás buscando, que está garantizado en el rango [0, 100] y una probabilidad mucho mayor de estar cerca del centro que los bordes.


3

Esta respuesta es realmente buena . Pero me gustaría publicar instrucciones de implementación (no estoy en JavaScript, así que espero que entiendan) para diferentes situaciones.


Suponga que tiene rangos y pesos para cada rango:

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

La información estática inicial, se puede almacenar en caché:

  1. Suma de todos los pesos (108 en muestra)
  2. Rango de selección de límites. Básicamente esta fórmula: Boundary[n] = Boundary[n - 1] + weigh[n - 1]y Boundary[0] = 0. Muestra tieneBoundary = {0, 1, 3, 103, 108}

Generación de números:

  1. Genere un número aleatorio a Npartir del rango [0, suma de todos los pesos).
  2. for (i = 0; i < size(Boundary) && N > Boundary[i + 1]; ++i)
  3. Tome el irango y genere un número aleatorio en ese rango.

Nota adicional para optimizaciones de rendimiento. No es necesario ordenar los rangos ni en orden ascendente ni descendente, por lo que para un rango de búsqueda de rango más rápido que tenga el mayor peso debe ir primero y uno con el menor peso debe ir último.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.