Obter um número aleatório focado no centro


238

É possível obter um número aleatório entre 1 e 100 e manter os resultados principalmente dentro da faixa de 40 a 60? Quero dizer, raramente sai desse intervalo, mas quero que ele esteja principalmente dentro desse intervalo ... É possível com JavaScript / jQuery?

Agora eu estou apenas usando o básico Math.random() * 100 + 1.





20
Eu gosto de onde esta pergunta está indo, mas acho que deveria ser mais específica. Deseja uma distribuição Z (curva de sino), uma distribuição triangular ou algum tipo de distribuição dente de serra? Existem várias possibilidades para responder a essa pergunta na minha opinião.
Patrick Roberts

12
Isso pode ser feito em javascript, mas com certeza não tem nada a ver com jQuery ... :)
A. Wolff

Respostas:


397

A maneira mais simples seria gerar dois números aleatórios de 0 a 50 e adicioná-los.

Isso fornece uma distribuição tendenciosa para 50, da mesma maneira que rola dois vieses de dados para 7.

De fato, usando um número maior de "dados" (como sugere @Falco) , é possível aproximar mais uma curva de sino:

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

Números aleatórios ponderados

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


12
Esta é uma solução fácil e rápida, que pode ser mais facilmente ponderada, adicionando mais números, por exemplo, 4 x (0-25) e fornecerá uma boa curva de sino para a distribuição!
Falco

8
Este é um pedaço fantástico de código. Eu acho que estou apaixonada por isso. Simples, rápido, eficiente; Ótima resposta. Obrigado por postar isso.
Ctwheels

14
Ótima resposta, mas caso alguém pretenda usá-la para gerar distribuição Normal, é bastante ineficiente (e você precisa transformá-la para obter a média e o desvio padrão desejados). Uma opção mais eficiente seria a transformação Box-Muller, que é bastante fácil de implementar e entender se você conhece um pouco de matemática.
Brendon

1
@RaziShaban É bastante intuitivo: existe apenas uma combinação de arremessos de mão que soma 2 (apenas olhos de cobra), mas existem 6 combinações diferentes que somam 7 (6-1, 5-2, 4-3, 3- 4, 2-5, 1-6). Se você generaliza para dados em frente e verso, o pico é sempre N + 1.
Barmar

2
@RaziShaban O estudo de variáveis ​​aleatórias é uma parte central da estatística. O fato de que, à medida que aumentamos os dados, nos aproximamos de uma distribuição normal, é o famoso Teorema do Limite Central .
BlueRaja - Danny Pflughoeft

48

Você tem boas respostas aqui que fornecem soluções específicas; deixe-me descrever para você a solução geral. O problema é:

  • Eu tenho uma fonte de números aleatórios distribuídos mais ou menos uniformemente entre 0 e 1.
  • Desejo produzir uma sequência de números aleatórios que seguem uma distribuição diferente.

A solução geral para esse problema é calcular a função quantil da sua distribuição desejada e aplicar a função quantil à saída da sua fonte uniforme.

A função quantil é o inverso da integral da sua função de distribuição desejada . A função de distribuição é a função em que a área sob uma parte da curva é igual à probabilidade de o item escolhido aleatoriamente estar nessa porção.

Eu dou um exemplo de como fazer isso aqui:

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

O código está em C #, mas os princípios se aplicam a qualquer idioma; deve ser fácil adaptar a solução ao JavaScript.


2
Eu gosto dessa abordagem. Pode querer acrescentar que existe uma biblioteca JavaScript que faz gerar Gaussian (e outros não-normais) distribuições: simjs.com/random.html
Floris

36

Tirar matrizes de números etc. não é eficiente. Você deve fazer um mapeamento que leva um número aleatório entre 0 a 100 e mapeia a distribuição necessária. Portanto, no seu caso, você pode levar para obter uma distribuição com mais valores no meio do seu intervalo.f(x)=-(1/25)x2+4x

Distribuição


2
Na verdade, não sabemos qual distribuição é necessária. "Principalmente 40-60" implica uma curva em forma de sino para mim.
Lefty

sim, você está certo, talvez você precise de um mapeamento melhor, mas isso é trivial
iCaramba

3
Aceito sua palavra, porque essa não é minha área de especialização. Você poderia ajustar a função e exibir a nova curva?
Lefty

1
@Lefty - Curva de sino simplificada xentre 0 e 100 (retirada desta pergunta ):y = (Math.sin(2 * Math.PI * (x/100 - 1/4)) + 1) / 2
Sphinxxx

@ Sphinxxx Isso não é uma curva de sino, é uma curva de pecado. Uma curva de sino nunca toca o eixo x.
BlueRaja - Danny Pflughoeft

17

Eu poderia fazer algo como configurar uma "chance" para o número ser permitido "fora dos limites". Neste exemplo, 20% de chance de o número ser 1-100, caso contrário, 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>

violino: http://jsfiddle.net/kbv39s9w/


5
Talvez alguém com mais detalhes estatísticos possa me corrigir e, embora isso atinja o que o OP está procurando (por isso votei), mas isso realmente não escolheria um # fora dos limites 20% das vezes, correto? Nesta solução, em 20% do tempo você teria a oportunidade de escolher um número de 1 a 100, que inclui 40 a 60. Isso não seria realmente (0,2 * 0,8) 16% para escolher um # fora dos limites, ou estou faltando alguma coisa?
Josh

Não, você está certo. É apenas a minha redação. Eu vou corrigir isso. Obrigado!
Bitwise Creative

1
@ Josh - Isso é muito bonito. Aqui está uma prova simples de como isso se parece com jsfiddle.net/v51z8sd5 . Ele mostrará a porcentagem de números encontrados fora dos limites e paira em torno de 0,16 (16%).
Travis J

15

Eu precisava resolver esse problema há alguns anos e minha solução foi mais fácil do que qualquer outra resposta.

Eu gerei 3 randoms entre os limites e calculei a média deles. Isso puxa o resultado em direção ao centro, mas deixa completamente possível alcançar as extremidades.


7
Como isso é melhor / diferente da resposta do BlueRaja? Lá, ele pega a soma de (2,3, ... qualquer número que você quiser) números aleatórios e calcula a média. O resultado é idêntico ao seu, quando você usa um BellFactorde 3.
Floris

@ floris bem, eu não codifico na família c de idiomas, para que a resposta nem parecesse estar fazendo a mesma coisa que a minha resposta até que eu a relesse agora. Criei meu método com um pouco de tentativa e erro e descobri que 3 randoms eram o número certo. Além disso, o meu pode ser feito em uma linha e ainda assim ser fácil de entender.
Lefty

2
Realmente? Você não acha que há semelhança entre JS e C? OK, bem, digamos que não posso falar QUALQUER dessas linguagens, nem Java, que, para mim, são todos semelhantes em comparação às linguagens com as quais estou familiarizado.
Lefty

1
Na verdade, eu estava realmente atraído apenas pelo título como algo que havia me resolvido e estava bastante orgulhoso da maneira como o fiz. Mais uma vez, eu não estava ciente de que é uma questão de js até você dizer isso. Sorte mesmo, porque minha técnica não depende da linguagem e algumas pessoas pensam que é uma resposta útil.
Lefty

5
JavaScript é realmente uma linguagem da família C ... mas ah, bem.
Joren 31/05

14

Ele parece estúpido, mas você pode usar rand duas vezes:

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 é possível. Faça um 1-100 aleatório. Se o número for <30, gere um número no intervalo de 1 a 100, se não gerar no intervalo de 40 a 60.


11

Existem várias maneiras diferentes de gerar esses números aleatórios. Uma maneira de fazer isso é calcular a soma de vários números uniformemente aleatórios. Quantos números aleatórios você soma e qual é o intervalo deles determinará a aparência da distribuição final.

Quanto mais números você resumir, mais será inclinado para o centro. O uso da soma de 1 número aleatório já foi proposto em sua pergunta, mas, como você percebe, não é direcionado para o centro do intervalo. Outras respostas propõem o uso da soma de 2 números aleatórios ou da soma de 3 números aleatórios .

Você pode obter ainda mais preconceitos em direção ao centro do intervalo, somando mais números aleatórios. No extremo, você pode pegar a soma de 99 números aleatórios, cada um com 0 ou 1. Isso seria uma distribuição binomial. (As distribuições binomiais podem, em certo sentido, ser vistas como a versão discreta das distribuições normais). Isso ainda pode, em teoria, abranger toda a gama, mas tem tanto viés em direção ao centro que você nunca deve esperar vê-lo atingir os pontos finais.

Essa abordagem significa que você pode ajustar o quanto você deseja.


8

Que tal usar algo como isto:

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>

A maneira como eu codifiquei permite definir algumas variáveis:
loops = número de resultados
tentativas = número de vezes que a função tentará obter um número entre 40-60 antes que pare de executar o loop while

Bônus adicionado: Usa do while! Awesomeness no seu melhor


8

Você pode escrever uma função que mapeie valores aleatórios entre [0, 1)de [1, 100]acordo com o peso. Considere este exemplo:

0,0-1,0 a 1-100 em percentagem de peso

Aqui, o valor é 0.95mapeado para o valor entre [61, 100].
De fato, temos .05 / .1 = 0.5, que, quando mapeados [61, 100], produz81 .

Aqui está a função:

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

Aqui está uma solução ponderada em 3/4 40-60 e 1/4 fora desse intervalo.

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, então decidi adicionar outra resposta, porque senti que minha última resposta, assim como a maioria das respostas aqui, usa algum tipo de meio meio estatístico de obter um retorno do resultado do tipo curva em sino. O código que forneço abaixo funciona da mesma maneira que quando você lança um dado. Portanto, é mais difícil obter 1 ou 99, mas mais fácil obter 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

Eu recomendo usar a distribuição beta para gerar um número entre 0-1 e depois escalá-lo. É bastante flexível e pode criar muitas formas diferentes de distribuição.

Aqui está um amostrador rápido e sujo:

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

A melhor solução para esse mesmo problema é a proposta por BlueRaja - Danny Pflughoeft, mas acho que também vale a pena mencionar uma solução mais rápida e mais geral.


Quando eu tenho que gerar números aleatórios (strings, pares de coordenadas, etc.) atendendo aos dois requisitos de

  1. O conjunto de resultados é bem pequeno. (não maior que 16 mil números)
  2. O conjunto de resultados é discreto. (apenas números inteiros)

Geralmente começo criando uma matriz de números (strings, pares de coordenadas etc.) cumprindo o requisito (no seu caso: uma matriz de números que contém os mais prováveis ​​várias vezes). Em seguida, escolha um item aleatório dessa matriz. Dessa forma, você só precisa chamar a função aleatória cara uma vez por item.


1
Se você for pré-preencher uma variedade de opções, também poderá embaralhá-las depois. Então você pode simplesmente pegá-los em ordem até acabar. Baralhe novamente se / quando você chegar ao final da lista.
Geobits

Embaralhar uma lista é uma tarefa que consome muito mais recursos do que escolher aleatoriamente um de seus elementos. É apenas uma boa escolha se a lista tiver que ser previsível.
mg30rg

1
Mas você faz isso apenas uma vez por ciclo da lista e não sempre. Se você pré-processar isso (já que você tem uma etapa de pré-processamento de qualquer maneira, presumo que esteja correto), será muito rápido obter cada número posteriormente. Você pode reorganizar sempre que tiver tempo ocioso ou saber que não precisará de um número aleatório nem um pouco. Apenas oferecendo-o como alternativa, ambos têm (des) vantagens.
Geobits

@ Geobits Se você fizer do seu jeito, os números de "probabilidade única" cairão "e, até o reassentamento, eles não poderão aparecer como resultado. (ou seja, se você simular o arremesso de dois dados, você não tem a menor chance de obter o número 2 mais do que duas vezes.)
mg30rg

1
Isso é um tanto melhor razão para não usá-lo, exceto para os raros aplicações onde que está tudo bem;)
Geobits

4

Distribuição

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

Solução

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

Solução 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

Você pode usar um número aleatório auxiliar para gerar números aleatórios em 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

Se você pode usar a gaussianfunção, use-a. Esta função retorna o número normal com average 0esigma 1 .

95% desse número estão dentro average +/- 2*sigma. Você average = 50e sigma = 5assim

randomNumber = 50 + 5*gaussian()

3

A melhor maneira de fazer isso é gerar um número aleatório que é distribuído igualmente em um determinado conjunto de números e, em seguida, aplicar uma função de projeção ao conjunto entre 0 e 100, onde é mais provável que a projeção atinja os números desejados.

Normalmente, a maneira matemática de conseguir isso é traçar uma função de probabilidade dos números que você deseja. Poderíamos usar a curva de sino, mas, para facilitar o cálculo, basta trabalhar com uma parábola invertida.

Vamos fazer uma parábola de modo que suas raízes estejam em 0 e 100 sem distorcer. Obtemos a seguinte equação:

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

Agora, toda a área sob a curva entre 0 e 100 é representativa do nosso primeiro conjunto, onde queremos que os números sejam gerados. Lá, a geração é completamente aleatória. Então, tudo o que precisamos fazer é encontrar os limites do nosso primeiro set.

O limite inferior é, obviamente, 0. O limite superior é a integral da nossa função em 100, que é

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)

Portanto, sabemos que precisamos gerar um número entre 0 e 166.666. Então, simplesmente precisamos pegar esse número e projetá-lo em nosso segundo conjunto, que está entre 0 e 100.

Sabemos que o número aleatório que geramos é parte integrante de nossa parábola com uma entrada x entre 0 e 100. Isso significa que simplesmente temos que assumir que o número aleatório é o resultado de F (x) e resolver x.

Nesse caso, F (x) é uma equação cúbica e na forma F(x) = ax^3 + bx^2 + cx + d = 0 , as seguintes instruções são verdadeiras:

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

Resolver isso para x fornece o número aleatório real que você está procurando, que é garantido no intervalo [0, 100] e tem uma probabilidade muito maior de estar próximo ao centro do que as bordas.


3

Esta resposta é realmente boa . Mas eu gostaria de postar instruções de implementação (não gosto de JavaScript, espero que você entenda) para situações diferentes.


Suponha que você tenha intervalos e pesos para cada intervalo:

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

Informações estáticas iniciais, podem ser armazenadas em cache:

  1. Soma de todos os pesos (108 na amostra)
  2. Limites de seleção de intervalo. É basicamente esta fórmula: Boundary[n] = Boundary[n - 1] + weigh[n - 1]e Boundary[0] = 0. Amostra temBoundary = {0, 1, 3, 103, 108}

Geração de número:

  1. Gere um número aleatório Ndo intervalo [0, Soma de todos os pesos).
  2. for (i = 0; i < size(Boundary) && N > Boundary[i + 1]; ++i)
  3. Pegue o iintervalo e gere um número aleatório nesse intervalo.

Nota adicional para otimizações de desempenho. As faixas não precisam ser ordenadas nem em ordem crescente nem decrescente; portanto, para obter um intervalo mais rápido de consulta de alcance com maior peso, deve-se primeiro e um com menor peso deve durar.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.