Generate multiple random numbers without repetition

Asked

Viewed 17,254 times

16

I’ve made some attempts to make a function that returns random numbers that don’t repeat themselves, I’ve made some attempts but none have succeeded, if you can show me a function that does that thank you.

  • What format do you look for in numbers? decimals or integers? In what range?

  • Integers, the range would be between 1 and 1000.

  • 3

    Junior my answer had a bug. With the warning from Bacco I corrected. Better use the new code.

  • http://answall.com/questions/5848

5 answers

21

First, generate an ordered list, in this example from 1 to 1000:

var maximo = 1000;

var i, arr = [];
for (i = 0; i < maximo; i++) {
   arr[i] = i + 1;
}

then shuffle the numbers within this array:

var p, n, tmp;
for (p = arr.length; p;) {
    n = Math.random() * p-- | 0;
    tmp = arr[n];
    arr[n] = arr[p];
    arr[p] = tmp;
}

Then just take the first items of the array, in order, from 1 until the amount of results you want, as they are already randomly shuffled, and no repeat.

See example with results on jsFiddle

adapted response of https://stackoverflow.com/questions/10694668/javascript-to-generate-50-no-repeat-random-numbers


If you need a few figures:

This technique is efficient even with few values, in this case simply shorten the loop to "shuffle" only the required amount of numbers:

for (p = quantidade; p;) {

In this way, only the first values, indicated by the variable, will be "shuffled" quantidade.


About the algorithm

This algorithm is the Fisher-Yates, as reinforced the colleague @woliveirajr in the comments, and on another issue, the colleague @Fabridamazio found an interesting analysis comparing Fisher-Yates to Shuffling Brute Force.

It’s worth a read:

Analysis of a Brute-Force Shuffle

  • Both are right, however, I preferred Sergio’s method because this method seems to be slower when you want to generate a large random number.

  • 4

    This solution is better than the other because, when the "free" numbers start to run out, the other solution will be slow (it keeps trying other random numbers until it finds a vacant one).

  • 1

    The name of this method is Fisher-Yates http://en.wikipedia.org/wiki/Fisher-Yates_shuffle

  • 2

    I’m leaving my collaboration, I did this Jsperf to analyze the performance of responses. I included another option where an array is generated and a random index is removed using Array.splice(). Bacco’s response is the most performative.

  • 1

    @fernandosavio I hope that the staff will see the difference between the point and the comma in the kkk results. I went here and it was "60.90" x "53,388" - which in pt_BR would be 60,90 x 53,388,00

17


The solution that occurs to me is to create an array and add to it the values already created/drawn, and look in the array if the value already exists before exporting a new one.

This solution allows you to draw values one by one as well as draw them all at once.

var sorteados = [];
var valorMaximo = 1000;

function criarUnico() {
    if (sorteados.length == valorMaximo) {
        if (confirm('Já não há mais! Quer recomeçar?')) sorteados = [];
        else return;
    }
    var sugestao = Math.ceil(Math.random() * valorMaximo); // Escolher um numero ao acaso
    while (sorteados.indexOf(sugestao) >= 0) {  // Enquanto o numero já existir, escolher outro
        sugestao = Math.ceil(Math.random() * valorMaximo);
    }
    sorteados.push(sugestao); // adicionar este numero à array de numeros sorteados para futura referência
    return sugestao; // devolver o numero único
}

So, to draw a number just use:

criarUnico(); // que retorna um valor único

Example

  • A suggestion... wouldn’t it be better to decrease the scope of Andom as the numbers are drawn? this way avoids being randomized several times numbers already drawn and take to find numbers that have not yet been, when there are already many numbers drawn.

  • 1

    Just remember that this function enters an infinite loop after all numbers 0.. 1000 have been drawn. (@Pauloroberto so does not look uniform)

  • 1

    What do you mean, "uniform"? @Guilhermebernal. By the way I agree with you but if the function insists on returning values that already exist it will be a huge drop in performance.

  • @Pauloroberto that if you limit the scope later, will prevent some values leave. Uniform in the sense that any value not drawn has the same probability of exiting.

  • 1

    @Guilhermebernal, thank you for the referral. I had added a solution to restart the count in jsFiddle. Now I’ve also added to the answer code.

  • @You don’t understand me. Of course it will have the same probability, you will be randomizing among the numbers that have not yet been drawn, which in this case will be the only ones that will proceed with the code. This would save performance and result in the same result with the same probability.

  • @Pauloroberto in this case does not have a good way to balance the range to exclude selected values. One way or another there will be many values already selected within the range. But yes, it is an acceptable although minimal optimization (imagine that numbers 1 and 1000 were drawn last).

  • Yeah, I don’t know how it could be done, but it would be a good performance.

  • @Pauloroberto I tried to do something as suggested (reduce the scope of the Andom to each draw), mapping the numbers already drawn to those not drawn and removed from the range, but the performance got worse... Except perhaps for some degenerate cases (e.g., among 1000000 choose 999999), this method is in fact the one that has the best performance. Comparative test in jsPerf.

  • 2

    I’m not sure but I think the has() method of the Set has more performance than the index() of the Array, if the goal is to optimize it can be a start https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set

Show 5 more comments

3

I use this solution of mine for simplicity, since it uses the method Sort(), native to the language:

var x = new Array(1000);
for(var i=1; i<1001; i++) {
    x[i] = i; 
}

x.sort(function(a,b){ return (Math.round(Math.random())-0.5); });

However, the method used by @Bacco will always be faster, since it is not necessary to recreate the array.

0

function sorteado(max,quant) {
    var numeroS = []
    while (numeroS.length < quant) {
      e = Math.ceil(Math.random() * max);
      if (numeroS.indexOf(e) == -1) {
        numeroS.push(e)
      }
    }
   return numeroS
}
console.log(sorteado(1000,16))

-1

Or you can run this way:

function getRandom(maxNum, qtdMax) {
  let n = 1;
  const lista = [];
  const val = () => Math.floor(Math.random() * maxNum + 1);
  
  do {
    while (n <= qtdMax) {
      let numero = val();

      if (lista.indexOf(numero) === -1) {
        lista.push(numero)
      } else {n -= 1};
      n += 1
    };
  } while (lista.length < qtdMax);

  lista.sort((a, b) => a - b);
  console.log(lista);
  return lista;
}

getRandom(60, 6);

In the above example, I left to run a 6-digit random combination (second function parameter) with value limit at 60 (first function parameter). So you can run the list only once for the amount of characters you want and the random number limit without making as many iterations. Detail, I put a Sort to organize the list upwards.

In a practical example, you can search for the data by index in the for loop, or unstructured:

const get = getRandom(60, 6);

for (const i in get) {
  const texto = `Index: ${i} = ${get[i]}`
  console.log(texto)
}

const [ num1 ] = get

console.log(num1)

Browser other questions tagged

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