Generate random numbers that result in a fixed sum

13

I need to generate random values for the inputs text of my table (remembering that it can have N rows), my code today can generate the numbers but I need the number generated to be at most X so that it does not exceed the value of the column Max

Ex: If the total is 200, the maximum value of test 1 is 150 and of test 2 is 130, the numbers must be generated so that the first value (test 1) is at most 150 and the second one (test 2 ) at most 130 the sum totals 200.

If the sum of the maximum values is not enough to arrive at the value entered, return the highest possible value for each field.

Does anyone see a practical solution to this case?

JSFiddle

    
asked by anonymous 24.02.2014 / 13:58

4 answers

6

The algorithm proposed in my other answer is functional but is not fair (ie does not distribute the value evenly between the lines). If there are many rows, the latter tend to be all with 0 . If there are few, the latter tend to get very large values. I will propose a workaround, limited to the case where the number to be distributed is integer and not too large , based on genetic algorithms :

  • Again, you have 200 (which I'll call alvo ) to be distributed between N lines. For simplicity, this time let's assume that the lines have no minimums, only maximums (if they do, refer to the first part of my other answer for a means of distributing the minimums).
  • Let's do a loop where at each iteration one of the lines will be chosen and receive 1 more. That is, the loop will execute 200 iterations.
    • First a "roulette" is created in which the area of each row in the roulette wheel is proportional to its maximum value.
    • Then one draws a line on the roulette wheel; it adds the value of 1 and reduces its maximum also in 1 . If it reaches zero, the roulette area for that line will become zero and it will no longer be drawn.

Example:

var alvo = 200;
var linhas = [{min: 0, max: 150}, {min:0, max: 130}];
var resultado = [0, 0];

function criarRoleta() {
    var ret = [];
    var soma = 0;
    for ( var i = 0 ; i < linhas.length ; i++ ) {
        var valor = linhas[i].max - resultado[i];
        ret.push(valor+soma);
        soma += valor;
    }
    return [ret,soma];
}

for ( var i = 0 ; i < alvo ; i++ ) {
    var roleta = criarRoleta();
    var sorteio = Math.floor(Math.random()*roleta[1]);
    for ( var t = 0 ; t < linhas.length ; t++ )
        if ( sorteio < roleta[0][t] ) {
            resultado[t]++;
            break;
        }
}

Example in jsFiddle . Note that in this case the distribution tends to be uniform, and proportional to the maximum of each line. Extreme distributions are possible, but rare - the fact that rows already drawn have their areas reduced, so that the other rows increase their chance of being chosen in the subsequent rounds.

Update: This method is equivalent to opening an urn, placing N balls of different colors in it (1 for each space available on each line), and leaving removing balls until they reach the total. That is, the probability of each line being drawn changes during the draw, so it is more likely that all X% will be full than a full 100% and some will be empty.

If on the other hand what interests you is that the chance of the elements falling on a line is proportional to the initial size of each line, then it is necessary to "put the drawn balls back in the ballot box: this is done by creating the roulette once, instead of re-creating it at each iteration (ie move the call from criarRoleta() out of the loop). In this case, you must check each draw if a line has reached its maximum and - if it has arrived - "remove all balls from that line of the ballot" (ie upgrade the roulette wheel so that the area of that row is zero, and the sum is adjusted from agreement).

    
24.02.2014 / 16:06
3

I think you should approach the problem from another angle: you actually have 200 (I'll call alvo ) and want to distribute this alvo to N rows, each line being subject to a maximum and perhaps a minimum. My suggestion then is:

  • Derive each of the minimums, and distribute between the lines (in their case there are no minima, then the value continues alvo and each line continues with 200 ). >
  • For each line 0 of the table:
    • Calculate% w / w%; i.e. what fraction of this i should necessarily be on this line, because if it is not going to exceed the maximums of the other lines.
    • Draw a number between minimo = alvo - soma(maximos[i+1:]) and alvo ; assign this value to the line and subtract from minimo .

Example:

var alvo = 200;
var linhas = [{min: 0, max: 150}, {min:0, max: 130}];
var resultado = [];

// Atribuindo os mínimos
for ( var i = 0 ; i < linhas.length ; i++ ) {
    resultado[i] = linhas[i].min;
    alvo -= linhas[i].min;
}

for ( var i = 0 ; i < linhas.length ; i++ ) {
    // o máximo que pode ser distribuído entre as linhas restantes
    var somaMax = 0;
    for ( var t = i+1 ; t < linhas.length ; t++ )
        somaMax += (linhas[t].max - linhas[t].min);

    // mínimo e máximo para esta linha
    var minimo = alvo < somaMax ? 0 : alvo - somaMax;
    var maximo = Math.min(alvo, linhas[i].max - linhas[i].min);

    // sorteia e atualiza o alvo
    var valor = Math.floor(Math.random()*(maximo-minimo) + minimo);
    resultado[i] += valor;
    alvo -= valor;
}

Example in jsFiddle .

    
24.02.2014 / 15:24
0

Viewing in w3schools I saw that to set a range for random and javascript you need to use:

Math.floor((Math.random()*100)+1); 

So you can create a function to do this using the maximum and minimum values as parameters like

function randomRange(min, max){
    return Math.floor((Math.random() * max) + min);
}

And use it this way:

randomRange(150,1000);
randomRange($("#inputMinimo").val(),$("#inputMaximo").val());
    
24.02.2014 / 14:10
0
Math.ceil((Math.random()*50)); // Onde 50 é o valor máximo (vai gerar números entre 0 e 50)
    
24.02.2014 / 15:20