Ottieni un numero casuale focalizzato sul centro


238

È possibile ottenere un numero casuale compreso tra 1 e 100 e mantenere i risultati principalmente nell'intervallo 40-60? Voglio dire, uscirà raramente da quell'intervallo, ma voglio che sia principalmente all'interno di quell'intervallo ... È possibile con JavaScript / jQuery?

In questo momento sto solo usando il basic Math.random() * 100 + 1.





20
Mi piace dove sta andando questa domanda, ma penso che dovrebbe essere più specifico. Vuoi una distribuzione Z (curva a campana), una distribuzione triangolare o una sorta di distribuzione a dente di sega? A mio avviso, ci sono molteplici possibilità di rispondere a questa domanda.
Patrick Roberts,

12
Questo può essere fatto in javascript ma sicuramente non ha nulla a che fare con jQuery ... :)
A. Wolff,

Risposte:


397

Il modo più semplice sarebbe generare due numeri casuali da 0 a 50 e sommarli.

Questo dà una distribuzione distorta verso 50, allo stesso modo lanciando due pregiudizi sui dadi verso 7.

Infatti, usando un numero maggiore di "dadi" (come suggerisce @Falco) , puoi fare un'approssimazione più vicina a una curva a campana:

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

Numeri casuali ponderati

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


12
Questa è una soluzione facile e veloce, che può essere facilmente ponderata di più, aggiungendo più numeri, ad esempio 4 x (0-25) e ti darà una bella curva a campana per la distribuzione!
Falco,

8
Questo è un fantastico pezzo di codice. Penso di esserne innamorato. Semplice, veloce, efficiente; Bella risposta. grazie per aver postato questo.
ctwheels,

14
Ottima risposta, ma nel caso in cui qualcuno intenda utilizzarlo per generare una distribuzione normale, è piuttosto inefficiente (e è necessario trasformarlo per ottenere la media e la deviazione standard desiderate). Un'opzione più efficiente sarebbe la trasformazione di Box-Muller, che è abbastanza facile da implementare e capire se conosci un po 'di matematica.
Brendon,

1
@RaziShaban È abbastanza intuitivo: c'è solo una combinazione di tiri di dado che aggiunge fino a 2 (solo occhi di serpente), ma ci sono 6 diverse combinazioni che aggiungono fino a 7 (6-1, 5-2, 4-3, 3- 4, 2-5, 1-6). Se generalizzi ai dadi con lato N, il picco è sempre N + 1.
Barmar,

2
@RaziShaban Lo studio delle variabili casuali è una parte centrale della statistica. Il fatto che quando aumentiamo i dadi ci avviciniamo a una distribuzione normale è il famoso Teorema del limite centrale .
BlueRaja - Danny Pflughoeft il

48

Hai delle buone risposte qui che offrono soluzioni specifiche; lascia che ti descriva la soluzione generale. Il problema è:

  • Ho una fonte di numeri casuali distribuiti più o meno uniformemente tra 0 e 1.
  • Vorrei produrre una sequenza di numeri casuali che seguono una diversa distribuzione.

La soluzione generale a questo problema è di elaborare la funzione quantile della distribuzione desiderata e quindi applicare la funzione quantile all'output della sorgente uniforme.

La funzione quantile è l' inverso della integrante della vostra desiderata funzione di distribuzione . La funzione di distribuzione è la funzione in cui l'area sotto una porzione della curva è uguale alla probabilità che l'elemento scelto casualmente sia in quella porzione.

Faccio un esempio di come farlo qui:

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

Il codice è in C #, ma i principi si applicano a qualsiasi lingua; dovrebbe essere semplice adattare la soluzione a JavaScript.


2
Mi piace questo approccio. Potrebbe voler aggiungere che esiste una libreria javascript che genera distribuzioni gaussiane (e altre non normali): simjs.com/random.html
Floris

36

Prendere matrici di numeri, ecc. Non è efficiente. Dovresti prendere una mappatura che richiede un numero casuale compreso tra 0 e 100 e le mappe per la distribuzione di cui hai bisogno. Quindi, nel tuo caso, potresti prendere una distribuzione con il maggior numero di valori nel mezzo del tuo intervallo.f(x)=-(1/25)x2+4x

Distribuzione


2
In realtà non sappiamo quale distribuzione è necessaria. "Principalmente 40-60" implica per me una curva a campana.
Lefty

sì, hai ragione, forse hai bisogno di una mappatura migliore, ma è banale
iCaramba,

3
Prenderò la tua parola per questo perché questa non è la mia area di competenza. Potresti regolare la funzione e visualizzare la nuova curva?
Lefty

1
@Lefty - Curva a campana semplificata xtra 0 e 100 (presa da questa domanda ):y = (Math.sin(2 * Math.PI * (x/100 - 1/4)) + 1) / 2
Sphinxxx,

@Sphinxxx Questa non è una curva a campana, è una curva sin. Una curva a campana non tocca mai l'asse x.
BlueRaja - Danny Pflughoeft,

17

Potrei fare qualcosa come impostare una "possibilità" per consentire al numero di andare "fuori dai limiti". In questo esempio, una probabilità del 20% il numero sarà 1-100, altrimenti 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
Forse qualcuno con maggiori dettagli statistici può correggermi, e sebbene questo raggiunga ciò che l'OP sta cercando (quindi ho votato), ma questo non sceglierebbe davvero un # fuori limite del 20% delle volte, giusto? In questa soluzione, il 20% delle volte avrai l'opportunità di scegliere un numero compreso tra 1 e 100, che include 40-60. Non sarebbe in realtà (0,2 * 0,8) il 16% di scegliere un # fuori limite o mi sto perdendo qualcosa?
Josh,

No, hai ragione. Sono solo le mie parole. Lo correggerò. Grazie!
Bitwise Creative

1
@Josh - È piuttosto perfetto. Ecco una semplice prova di quello che sembra jsfiddle.net/v51z8sd5 . Mostrerà la percentuale di numeri trovati fuori limite e si aggira intorno allo 0,16 (16%).
Travis J,

15

Avevo bisogno di risolvere questo problema qualche anno fa e la mia soluzione era più semplice di qualsiasi altra risposta.

Ho generato 3 randoms tra i limiti e li ho mediati. Questo tira il risultato verso il centro ma lascia completamente possibile raggiungere le estremità.


7
In che modo è migliore / diverso dalla risposta di BlueRaja? Lì, prende la somma di (2,3, ... qualsiasi numero desiderato) numeri casuali e prende la media. Il risultato è identico al tuo quando usi un valore BellFactordi 3.
Floris,

@Floris bene, non scrivo codice nella famiglia di lingue c, quindi quella risposta non sembrava nemmeno che stesse facendo la stessa cosa della mia risposta fino a quando non la rileggo ora. Ho creato il mio metodo con un po 'di tentativi ed ho scoperto che 3 randoms erano il numero giusto. Inoltre, il mio può essere fatto in una riga ed essere ancora facile da capire.
Lefty

2
Veramente? Non pensi che ci sia qualche somiglianza tra JS e C? OK, bene, diciamo solo che non posso parlare di QUESTE lingue, né Java, che per me sono tutte simili rispetto alle lingue che conosco.
Lefty

1
Il punto giusto, in realtà sono stato attratto solo dal titolo come qualcosa che avevo risolto da solo ed ero abbastanza orgoglioso di come l'ho fatto. Ancora una volta, non sapevo che fosse una domanda js finché non l'hai detto. Fortunatamente davvero, perché la mia tecnica non dipende dalla lingua e alcune persone sembrano pensare che sia una risposta utile.
Lefty

5
JavaScript in realtà è un linguaggio della famiglia C ... ma ah bene.
Joren,

14

E ' Sembra stupido, ma è possibile utilizzare rand due volte:

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

Sicuro è possibile Fai un 1-100 casuale. Se il numero è <30, quindi generare il numero nell'intervallo 1-100, se non generare nell'intervallo 40-60.


11

Esistono molti modi diversi per generare tali numeri casuali. Un modo per farlo è calcolare la somma di più numeri uniformemente casuali. Quanti numeri casuali sommi e qual è il loro intervallo determinerà l'aspetto della distribuzione finale.

Più numeri si sommano, più sarà distorto verso il centro. L'uso della somma di 1 numero casuale era già stato proposto nella tua domanda, ma come noti non è distorto verso il centro dell'intervallo. Altre risposte hanno proposto di utilizzare la somma di 2 numeri casuali o la somma di 3 numeri casuali .

Puoi ottenere ancora più distorsioni verso il centro dell'intervallo prendendo la somma di più numeri casuali. All'estremo si potrebbe prendere la somma di 99 numeri casuali che erano entrambi 0 o 1. Sarebbe una distribuzione binomiale. (Le distribuzioni binomiali possono in qualche modo essere viste come la versione discreta delle distribuzioni normali). In teoria questo può ancora coprire l'intera gamma, ma ha così tanta inclinazione verso il centro che non dovresti mai aspettarti di vederlo raggiungere gli endpoint.

Questo approccio significa che puoi modificare quanta distorsione vuoi.


8

Che ne dici di usare qualcosa del genere:

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>

Il modo in cui l'ho codificato ti consente di impostare un paio di variabili:
loop = numero di risultati
try = numero di volte in cui la funzione proverà a ottenere un numero compreso tra 40-60 prima di interrompere l'esecuzione nel ciclo while

Bonus aggiunto: usa do while !!! La bellezza al suo meglio


8

È possibile scrivere una funzione che mappa i valori casuali tra [0, 1)in [1, 100]base al peso. Considera questo esempio:

Da 0,0-1,0 a 1-100 in peso percentuale

Qui, il valore viene 0.95mappato tra i valori [61, 100].
In effetti abbiamo .05 / .1 = 0.5, che, quando mappato [61, 100], produce81 .

Ecco la funzione:

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

Ecco una soluzione ponderata a 3/4 40-60 e 1/4 al di fuori di tale intervallo.

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, quindi ho deciso di aggiungere un'altra risposta perché mi sentivo come la mia ultima risposta, così come la maggior parte delle risposte qui, usano una sorta di mezzo modo semi-statistico per ottenere un risultato di tipo a campana. Il codice che fornisco di seguito funziona allo stesso modo di quando tiri un dado. Pertanto, è più difficile ottenere 1 o 99, ma più facile ottenerne 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

Consiglierei di usare la distribuzione beta per generare un numero compreso tra 0-1, quindi ridimensionarlo. È abbastanza flessibile e può creare molte forme diverse di distribuzioni.

Ecco un campionatore veloce e sporco:

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 migliore soluzione per affrontare proprio questo problema è quella proposta da BlueRaja - Danny Pflughoeft, ma penso che valga la pena menzionare anche una soluzione un po 'più veloce e più generale.


Quando devo generare numeri casuali (stringhe, coppie di coordinate, ecc.) Che soddisfino i due requisiti di

  1. Il set di risultati è piuttosto piccolo. (non più grande di 16K numeri)
  2. Il set di risultati è discreto. (come solo numeri interi)

Di solito inizio creando un array di numeri (stringhe, coppie di coordinate, ecc.) Che soddisfano il requisito (nel tuo caso: un array di numeri che contiene più probabili più volte), quindi scelgo un elemento casuale di quell'array. In questo modo, devi chiamare la costosa funzione casuale una sola volta per articolo.


1
Se hai intenzione di precompilare una serie di scelte, potresti anche mescolarle in seguito. Quindi puoi semplicemente afferrarli in ordine finché non finisci. Mischia di nuovo se / quando premi la fine dell'elenco.
Geobits

@Geobits Mescolare un elenco è un'attività molto più dispendiosa in termini di risorse rispetto alla scelta casuale di uno dei suoi elementi. È solo una buona scelta se l'elenco deve essere prevedibile.
mg30rg

1
Ma lo fai solo una volta per ciclo dell'elenco anziché ogni volta. Se preprocedi questo (dato che hai comunque una fase di preelaborazione, presumo che vada bene), allora è molto veloce ottenere ogni numero in seguito. Puoi rimescolare ogni volta che hai dei tempi morti o sapere che non avrai bisogno di un numero casuale per un po '. Offrendolo solo in alternativa, entrambi hanno (dis) vantaggi.
Geobits

@Geobits Se lo fai a modo tuo, i numeri della "probabilità singola" "cadranno" e fino al rimescolamento non possono apparire di conseguenza. (cioè se simuli il lancio di due dadi, non avrai la minima possibilità di ottenere il numero 2 più di due volte.)
mg30rg

1
Questa è una ragione molto migliore per non usarla, tranne per le rare applicazioni in cui va bene;)
Geobits

4

Distribuzione

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

Soluzione

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

Soluzione generica

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

È possibile utilizzare un numero casuale di supporto per generare numeri casuali in 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

Se puoi usare la gaussianfunzione, usala. Questa funzione restituisce un numero normale con average 0esigma 1 .

Il 95% di questo numero è compreso average +/- 2*sigma. Tuo average = 50e sigma = 5così

randomNumber = 50 + 5*gaussian()

3

Il modo migliore per farlo è generare un numero casuale che sia distribuito equamente in un determinato insieme di numeri, quindi applicare una funzione di proiezione all'insieme tra 0 e 100 in cui è più probabile che la proiezione colpisca i numeri desiderati.

In genere il modo matematico per raggiungere questo obiettivo è tracciare una funzione di probabilità dei numeri desiderati. Potremmo usare la curva a campana, ma per semplificare il calcolo basta lavorare con una parabola capovolta.

Facciamo una parabola in modo tale che le sue radici siano a 0 e 100 senza inclinarla. Otteniamo la seguente equazione:

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

Ora, tutta l'area sotto la curva tra 0 e 100 è rappresentativa del nostro primo set in cui vogliamo che vengano generati i numeri. Lì, la generazione è completamente casuale. Quindi, tutto ciò che dobbiamo fare è trovare i limiti del nostro primo set.

Il limite inferiore è, ovviamente, 0. Il limite superiore è l'integrale della nostra funzione a 100, che è

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)

Quindi sappiamo che dobbiamo generare un numero compreso tra 0 e 166.666. Quindi, dobbiamo semplicemente prendere quel numero e proiettarlo sul nostro secondo set, che è tra 0 e 100.

Sappiamo che il numero casuale che abbiamo generato è parte integrante della nostra parabola con un input x compreso tra 0 e 100. Ciò significa che dobbiamo semplicemente supporre che il numero casuale sia il risultato di F (x) e risolvere per x.

In questo caso, F (x) è un'equazione cubica e, nella forma F(x) = ax^3 + bx^2 + cx + d = 0, sono vere le seguenti affermazioni:

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

Risolvendo questo per x si ottiene il numero casuale effettivo che si sta cercando, che è garantito essere nell'intervallo [0, 100] e una probabilità molto più alta di essere vicino al centro rispetto ai bordi.


3

Questa risposta è davvero buona . Ma vorrei pubblicare le istruzioni di implementazione (non sono in JavaScript, quindi spero che capirai) per situazioni diverse.


Supponiamo di avere intervalli e pesi per ogni intervallo:

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

Informazioni statiche iniziali, possono essere memorizzate nella cache:

  1. Somma di tutti i pesi (108 nel campione)
  2. Confini di selezione dell'intervallo. Fondamentalmente questa formula: Boundary[n] = Boundary[n - 1] + weigh[n - 1]e Boundary[0] = 0. Campione haBoundary = {0, 1, 3, 103, 108}

Generazione del numero:

  1. Genera un numero casuale Ndall'intervallo [0, somma di tutti i pesi).
  2. for (i = 0; i < size(Boundary) && N > Boundary[i + 1]; ++i)
  3. Prendi il'intervallo e genera un numero casuale in quell'intervallo.

Nota aggiuntiva per le ottimizzazioni delle prestazioni. Gli intervalli non devono essere ordinati né in ordine crescente né in ordine decrescente, quindi per un intervallo di ricerca di intervallo più veloce che ha il peso più elevato dovrebbe andare per primo e uno con il peso più basso dovrebbe andare per ultimo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.