Generate random numbers that result in a fixed sum

Asked

Viewed 3,372 times

13

I need to generate random values for the inputs text of my table (remembering that it can have N lines), my code today can generate the numbers but I need the generated number 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 test 2 is 130, the numbers must be generated so that the first value (test 1) is at most 150 and the second (test 2) at most 130 the sum totals 200.

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

Someone sees a practical solution to this case?

Jsfiddle

  • Are always two values that should result in a third? Or do you have N values each with a different limit and that the sum should give a fixed total?

  • 2

    I can have N lines/values @Guilhermebernal

  • 1

    I don’t know java and I’m not sure I understand your example. But I think this link will help you answer your question. http://stackoverflow.com/a/5623492

  • @Marcosbanik It’s a great link, you might want to put as an answer (although it doesn’t include any Javascript code - since the doubt of the OP is in the algorithm, not in the encoding). I just don’t know if it’ll solve the problem whole - because the "partitions" in this case would have to be dirty to upper limits.

  • @mgibsonbr, I’m not sure yet to write an answer. The problem has 3 parameters. Sum, N and max. If $Sum <= N * max$ the link algorithm solves the problem. If $Soma > N * max$ it can appeal to one, and only one, element greater than max. That’s it?

  • @Marcosbanik By my understanding of the question, if the sum is greater than the maxima then each line should have the maximum value and ready (even if the sum is lower than the desired one). The difficulty here is that there is a different maxim per line - and not a max global.

  • In fact that algorithm does not solve even the case $Soma <= N * max$, but I still think it can be adapted. Just can’t guarantee that the first vector will solve the problem.

Show 2 more comments

4 answers

7


The algorithm proposed in my another answer is functional but it’s not righteous (i.e. does not distribute the value evenly between lines). If there are many lines, the latter tend to be all with 0. If there are few, the latter tend to get very large values. I will propose an alternative solution, limited to the case where the number to be distributed is whole and not excessively large, based on the "roulette" of genetic algorithms:

  • Once again, you have 200 (which I will call alvo) to be distributed between N lines. For simplification, this time let’s assume that the lines have no minimum, only maximum (if you have, refer to the first part of my other answer to a means of distributing the minimum).
  • Let’s make a loop in which at each iteration one of the lines will be chosen and receive 1 more. That is, the loop will execute 200 iterations.
    • A "roulette" is first created in which the area of each line in the roulette is proportional to its maximum value.
    • Then draw a line at roulette; add the value of 1 and reduces its maximum also in 1. If it reaches zero, the roulette area intended 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 lines already drawn have their areas reduced contributes to the other lines increase their chance of being chosen in the following rounds.

Updating: this method is equivalent to opening an urn, putting in it N balls of different colors (1 for each space available in each row), and go out removing balls until you reach the total. That is, the probability of each line being drawn changes during the draw, so that it is more likely that they will all be X% full than a 100% full and other more empty.

If on the other hand what you care about is that the chance of the elements falling on a line is proportional to the size initial of each line, then it is necessary to "put the drawn balls back into the urn": this is done by creating the roulette once, instead of recreating them at each iteration (i.e. move the call to criarRoleta() out of the loop). In this case, it is necessary to verify at each draw whether a line has reached its maximum and - if it has - "remove all the balls from that line from the urn" (i.e. upgrade the roulette so that the area of that line is zero, and the sum is adjusted accordingly).

  • Again being boring: The problem here is that the probability of each field being drawn should be proportional to its limit. If you have a field with low limit, for example, it will be set with the limit value or very close (since the draw is uniform).

  • @Guilhermebernal The draw is not uniform. The area of the roulette intended for each line is proportional to the line boundary. Like I said, it’s the same technique used in genetic algorithms. (the only difference here is that I’m changing the limit so as to favor those not yet drawn; but this can be removed if convenient...) P.S. Try the example fiddle by placing the maxima 300 and 30

  • 3

    Note here: http://jsfiddle.net/G9U8P/6/. The three possible solutions are 88,12, 89,11 and 90,10. And they should all have the same probability, but the first good happens much more frequency while the latter almost never occurs.

  • 1

    "all should have the same probability" in fact the distribution requirements are not well defined. One measure is the one you proposed ("every possible distribution must occur with the same probability"), another would be ("the lines must have uniform values, but respecting the limits of each line"), and I believe that other requirements would be possible. But I agree that my solution does not satisfy your requirement.

  • 1

    In fact, the problem is not exactly clear at this point, and I did not find a way to generate uniformly as I mentioned in non-exportional time. So yes, this is a good solution, and a good approximation if the values of the limits are close (as in his example).

  • @mgibsonbr This solution, however much it does not have the probability proportional to its limit, is just as you said and meets my need. Thank you very much!

  • Thank you also @Guilhermebernal for your analysis!

Show 2 more comments

3

I think you should approach the problem from another angle: you actually have 200 (I’ll call it alvo) and wants distribute that alvo for N lines, each line being subject to a maximum and perhaps a minimum. My suggestion then is:

  • Deducts from its value (alvo) each of the minimum, and distribute between the lines (in your case there is no minimum, then the value continues 200 and each line continues with 0).
  • For each line i table:
    • Calculate a minimo = alvo - soma(maximos[i+1:]); i.e. what fraction of this alvo must necessarily be on that line because if it is not will exceed the maximum of the other lines.
    • Draw a number between minimo and min(alvo, maximos[i]); assigns this value to the line and subtracts from the alvo.

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.

  • 4

    A serious problem here is that if the number of lines is large, the latter will have a strong tendency to be zeros, or close to zeroes. (I’m having trouble finding a way to do this evenly (in acceptable time))

  • @Guilhermebernal I agree, the algorithm proposed here is not righteous. I have no suggestions in this regard either... :(

  • @mgibsonbr For sure your suggestion was great and at least I was able to open my head a little more to solve my problem.

  • Thank you also @Guilhermebernal for your analysis, this trend can really disrupt the functionality I seek.

  • @Alessandrogomes if this "partiality" is a problem, take a look at mine another answer also. It can be classified as "inefficient", so it is up to you to determine in practice whether it is applicable or not (according to your input values).

0

Seeing at w3schools I saw that to set a range for Random and 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 this

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

And use it this way:

randomRange(150,1000);
randomRange($("#inputMinimo").val(),$("#inputMaximo").val());
  • How to generate the number based on the min and max value I already know how it works, I wanted to know how to apply in my case in a practical way, but thank you very much for your reply.

  • 3

    Note that this generates values down below of max. This limit is excluding. (the min is included)

  • 1

    In your second code, you didn’t mean Math.random()*(max-min) + min? Ex.: by your code, if min = 10, max = 20, he could draw lots 29 (19 + 10).

0

Math.ceil((Math.random()*50)); // Onde 50 é o valor máximo (vai gerar números entre 0 e 50)

Browser other questions tagged

You are not signed in. Login or sign up in order to post.