在JS中生成非重复随机数

I have the following function

function randomNum(max, used){
 newNum = Math.floor(Math.random() * max + 1);

  if($.inArray(newNum, used) === -1){
   console.log(newNum + " is not in array");
   return newNum;

  }else{
   return randomNum(max,used);
  }
}

Basically I am creating a random number between 1 - 10 and checking to see if that number has already been created, by adding it to an array and checking the new created number against it. I call it by adding it to a variable..

UPDATED:
for(var i=0;i < 10;i++){

   randNum = randomNum(10, usedNums);
   usedNums.push(randNum);

   //do something with ranNum
}

This works, but in Chrome I get the following error:

Uncaught RangeError: Maximum call stack size exceeded

Which I guess it's because I am calling the function inside itself too many times. Which means my code is no good.

Can someone help me with the logic? what's a best way to make sure my numbers are not repeating?

If I understand right then you're just looking for a permutation (i.e. the numbers randomised with no repeats) of the numbers 1-10? Maybe try generating a randomised list of those numbers, once, at the start, and then just working your way through those?

This will calculate a random permutation of the numbers in nums:

var nums = [1,2,3,4,5,6,7,8,9,10],
    ranNums = [],
    i = nums.length,
    j = 0;

while (i--) {
    j = Math.floor(Math.random() * (i+1));
    ranNums.push(nums[j]);
    nums.splice(j,1);
}

So, for example, if you were looking for random numbers between 1 - 20 that were also even, then you could use:

nums = [2,4,6,8,10,12,14,16,18,20];

Then just read through ranNums in order to recall the random numbers.

This runs no risk of it taking increasingly longer to find unused numbers, as you were finding in your approach.

EDIT: After reading this and running a test on jsperf, it seems like a much better way of doing this is a Fisher–Yates Shuffle:

function shuffle(array) {
    var i = array.length,
        j = 0,
        temp;

    while (i--) {

        j = Math.floor(Math.random() * (i+1));

        // swap randomly chosen element with current element
        temp = array[i];
        array[i] = array[j];
        array[j] = temp;

    }

    return array;
}

var ranNums = shuffle([1,2,3,4,5,6,7,8,9,10]);

Basically, it's more efficient by avoiding the use of 'expensive' array operations.

BONUS EDIT: Another possibility is using generators (assuming you have support):

function* shuffle(array) {

    var i = array.length;

    while (i--) {
        yield array.splice(Math.floor(Math.random() * (i+1)), 1)[0];
    }

}

Then to use:

var ranNums = shuffle([1,2,3,4,5,6,7,8,9,10]);

ranNums.next().value;    // first random number from array
ranNums.next().value;    // second random number from array
ranNums.next().value;    // etc.

where ranNums.next().value will eventually evaluate to undefined once you've run through all the elements in the shuffled array.

Overall this won't be as efficient as the Fisher–Yates Shuffle because you're still splice-ing an array. But the difference is that you're now doing that work only when you need it rather than doing it all upfront, so depending upon your use case, this might be better.

The issue is that as you approach saturation you begin to take longer and longer to generate a unique number "randomly". For instance, in the example you provided above the max is 10. Once the used number array contains 8 numbers it can potentially take a long time for the 9th and 10th to be found. This is probably where the maximum call stack error is being generated.

jsFiddle Demo showing iteration count being maxed

By iterating inside of your recursion, you can see that a large amount of execution occurs when the array is completely saturated, but the function is called. In this scenario, the function should exit.

jsFiddle Demo with early break

if( used.length >= max ) return undefined;

And one last way to accomplish both the iteration checks and the infinite recursion would be like this jsFiddle Demo:

function randomNum(max, used, calls){
 if( calls == void 0 ) calls = 0;
 if( calls++ > 10000 ) return undefined;
 if( used.length >= max ) return undefined;
 var newNum = Math.floor(Math.random() * max + 1);
 if($.inArray(newNum, used) === -1){
   return newNum;
 }else{
   return randomNum(max,used,calls);
 }
}
function randomNumbers(max) {
    function range(upTo) {
        var result = [];
        for(var i = 0; i < upTo; i++) result.push(i);
        return result;
    }
    function shuffle(o){
        for(var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
        return o;
    }
    var myArr = shuffle(range(max));
    return function() {
        return myArr.shift();
    };
}

Built a little test, try this on jsfiddle:

var randoms = randomNumbers(10),
    rand = randoms(),
    result = [];
while(rand != null) {
    result.push(rand);
    rand = randoms();
}
console.log(result);

Shuffle function courtesy of dzone.com.

You don't really want a lost of random numbers. Truly random numbers must be able to repeat.

Truly random number are like throwing dice. Any number can come up next.

Shuffled numbers are like drawing playing cards. Each number can come up only once.

What you are really asking for is to shuffle a list of numbers and then use the first so many numbers from the shuffled list.

Think of making a list of numbers in order, and then using the random number generator to randomly select a number from a copy of that list. Each time, put the selected number at the end of your new list and remove it from the copy of the old list, shortening that list. When you are done, the new list will contain the shuffled numbers and the copy of the old list will be empty.

Alternately, you can take the number selected and use it immediately, shortening the copy of the list by removing the used number. Because you have removed the number from the list, it can't come up again.

This is how I achieve it using underscore.js

To get n integers from min to max values. Where n is the size argument.

var randomNonRepeatingIntFromInterval = function(min, max, size) {
    var values = [];

    while (values.length < size) {
      values.push(Math.floor(Math.random() * ( max - min + 1) + min));

      values = _.uniq(values);
    }

    return values;
  }
<!DOCTYPE html>
<html>
<body>

<h2>JavaScript Math.random()</h2>

<p>Math.random() returns a random number between 0 (included) and 1 (excluded):</p>

<p id="demo"></p>

<script>


var storeArray = []

function callRamdom(){
    var randomNumber = Math.floor(Math.random() * 5);   
    return randomNumber;
}

function randomStore(){ 

    var localValue = callRamdom()
    var status = false;
    for(i=0;i<5; i++){
    var aa = storeArray[i];
        if(aa!=localValue){
            console.log(storeArray[i]+"hhhhh"+ localValue); 
            if(i==4){
                status=true;        
            }

        }   
        else
        break;

    }

    if(status==true){

        storeArray.push(localValue);    
    }
    if(storeArray.length!=5){
        randomStore();
    }   

    return storeArray;
}



document.getElementById("demo").innerHTML = randomStore();


</script>

</body>
</html>

Sorry this is a new answer to an old question, but this can be done more efficiently with a map. What you're after is random selection rather than non-repeating random. Non-repeating random is nonsensical.

Where _a is the collection, and r is not part of the collection, we lambda the random value r:

function aRandom(f){
  var r = Math.random();
  aRandom._a[r] ? aRandom(f) : f(r,aRandom._a[r] = 1);
}
aRandom._a = {};

//usage:
aRandom(function(r){ console.log(r) });

Redefine aRandom._a when the browser gets sluggish. To avoid eventual sluggishness, one should really use an UUID generation algo with sufficient entropy so that chances of repeat are effectively zero, rather than brute forcing differentiability. I chose the function name aRandom because latin prefix A- means "away from." Since the more it's used, the further away from random the output. The function produces one million unique values in 2100 ms on a Macbook.

Advantage of the above solution is no need to limit the set. As well, multiple callers can use it at the same time and assume their values are different from all other callers. This is handy for such things as noise jittering distributions with insured no overlaps.

However, it can be modified to return integers as well, so as to restrict ram use to the length supplied:

function aRandom(f,c){
  var r = Math.floor(Math.random()*c);
  aRandom._a[r] ? aRandom(f,c) : f(r,aRandom._a[r] = 1);
}
aRandom._a = {};


//usage:
var len = 10;
var resultset = [];
for(var i =0; i< len; i++){
  aRandom(function(r){ resultset.push(r); }, len);
}
console.log(resultset);

This will do what you're looking for:

let anArrayOfUniqueNumbers = [];

let numberGenerator = function(arr) {
  if (arr.length >= 10) return;
  let newNumber = Math.floor(Math.random() * 10 + 1);
  if (arr.indexOf(newNumber) < 0) {
    arr.push(newNumber);
  }
  numberGenerator(arr);
};

numberGenerator(anArrayOfUniqueNumbers);

We have:

  • a new array
  • a function which takes an array as an argument
This function will:
  • Check if the array it's operating on already has ten indices, and if not:
  • Generate a random number between 1-10
  • If that random number isn't in the array yet, push it into the array
  • Run again

Because of the guard clause (if (arr.length >= 10) return;), the function will stop executing once the parameters have been met.