For example I would like to generate 5 unique numbers between 1 & 10. The results should be 5 numbers from 1 to 10(for example 2 3 4 8 10).
If the range is very large, and the number of values you want is pretty small (like, 5 distinct values in the range 1 ... 1000000) then you can try generating random numbers in the range and throw away (unlikely) duplicates until you have 5. The problem with the "keep trying" approach is that there's a teeny tiny chance you could spend a lot of time with a string of random values that give you a lot of duplicates. For a big range of values, that's so unlikely that it's probably worth risking it unless the software is giving oxygen to trauma patients or something.
function generateNumbers(resultCount, from, to) {
var result = []; // holds the final result
var isAlreadyPicked = []; // quick lookup for the picked numbers
// holds substitutes for when we pick a number that has been picked before
var substitutes = [];
for (var i = 0; i < resultCount; i++) {
// pick a random number
var number = Math.floor(Math.random() * (to - from)) + from;
if(isAlreadyPicked[number]) {
// pick a number from the ones we skipped at the end of the range.
var j = Math.floor(Math.random() * substitutes.length);
number = substitutes[j];
substitutes.splice(j, 1);
}
// Save the number and mark it as being picked.
result.push(number);
isAlreadyPicked[number] = true;
// decrease the range. (Because there is 1 less number to choose from)
to -= 1;
if(!isAlreadyPicked[to]) {
// Save a substitute for when we pick the same number in a next iteration.
substitutes.push(to);
}
}
return result;
}
It works by decreasing the top of the range with each iteration. If the number that was picked in the previous iteration already exists in the result, just change it to one of the top numbers we left out of the range (There will always be only 1 number in there that wasn't picked before). This is also a true random number because it virtually existed in the place of the number that was picked in one of the previous iterations.
In my opinion this is the best solution because:
It doesn't allocate a lot of memory by assigning all numbers to an array and shuffling that array.
It has only X iterations so hook it up to this breathing machine :)
It is true random, my previous answer where I added 1 if the number was already picked was not. Because the number that is after the number that was picked in the previous iteration had twice the chance of getting picked than the other numbers.
EDIT 1: I was testing this algorithm on a large number of cases and I made a small mistake. There was an edge case where sometimes the numbers were not unique. It happened when one of the top numbers in the range was already picked. I updated my code.
EDIT 2: In case you're interested: here's the code I used to test this:
function test(cases, size) {
var errors = 0;
for (var i = 0; i < cases; i++) {
var start = Math.floor(Math.random() * size);
var end = start + Math.floor(Math.random() * size);
var count = Math.floor(Math.random() * (end - start));
var nrs = generateNumbers(count, start, end);
console.log('testing', start, end, count, nrs);
test:
for (var j = 0; j < count; j++) {
var testedNumber = nrs[j];
if(testedNumber < start || testedNumber >= end) {
console.error('out of range', testedNumber);
errors += 1;
break test;
}
for (var k = 0; k < count; k++) {
if(j !== k && nrs[k] === testedNumber) {
console.error('picked twice', testedNumber);
errors += 1;
break test;
}
}
}
}
console.log('all tests finished | errors:', errors)
}
test(1000, 20);
EDIT 3: Pointy suggested to use a faster algorithm to look if a number is unique. I updated the sample with his/her suggestion. Thanks Pointy!
EDIT 4: Made the algorithm a bit more efficient by removing the inner loop and replacing it with a substitutes array. Funny thing is, this algorithm can actually be used as the shuffling algorithm in the previous answer :)
This question intrigues me a lot. I have had the need for an algorithm like this before. This is actually the first time I found a solution that satisfies me. I did some research on this subject and this seems to be a variant of the Fisher-Yates shuffle algorithm.
EDIT 5: Changed the algorithm to pick a random number from the substitutes in stead of just the first.
Another method is to produce random numbers and only add them to the returning array if it does not already include them.
function randomRange(from, to, leng){
var tem, A= [], L= 0, i= 0;
randomRangeLoop:
while(L< leng){
tem= Math.floor(Math.random()*to)+from;
i= 0;
while(i<L){
if(A[i++]=== tem) continue randomRangeLoop;
}
A[L++]= tem;
}
return A;
}
alert(randomRange(1, 10, 5))
/* returned value: (Array) 8,6,1,3,5 */