views:

1570

answers:

10

Hey guys, i have trouble generating unique random numbers using js. Can someone lend me a hand?

+6  A: 

Generate permutation of 100 numbers and then choose serially.

Use Knuth Shuffle(aka the Fisher-Yates shuffle) Algorithm.

JavaScript:

  function fisherYates ( myArray,stop_count ) {
  var i = myArray.length;
  if ( i == 0 ) return false;
  int c = 0;
  while ( --i ) {
     var j = Math.floor( Math.random() * ( i + 1 ) );
     var tempi = myArray[i];
     var tempj = myArray[j];
     myArray[i] = tempj;
     myArray[j] = tempi;

     // Edited thanks to Frerich Raabe
     c++;
     if(c == stop_count)return;

   }
}

CODE COPIED FROM LINK.

EDIT:

Improved code:

function fisherYates(myArray,nb_picks)
{
    for (i = myArray.length-1; i > 1  ; i--)
    {
        var r = Math.floor(Math.random()*i);
        var t = myArray[i];
        myArray[i] = myArray[r];
        myArray[r] = t;
    }

    return myArray.slice(0,nb_picks);
}

Potential problem:

Suppose we have array of 100 numbers {e.g. [1,2,3...100]} and we stop swapping after 8 swaps; then most of the times array will look like {1,2,3,76,5,6,7,8,...numbers here will be shuffled ...10}.

Because every number will be swapped with probability 1/100 so prob. of swapping first 8 numbers is 8/100 whereas prob. of swapping other 92 is 92/100.

But if we run algorithm for full array then we are sure (almost)every entry is swapped.

Otherwise we face a question : which 8 numbers to choose?

TheMachineCharmer
This approach is correct but suboptimal: you could stop shuffling after eight swaps, since you need only eight random numbers. The code above swaps the entire array (in this scenario, 100 elements).
Frerich Raabe
@Frerich Raabe : Great idea! I ll edit answer to reflect it!
TheMachineCharmer
The code could be seriously improved. Return values, side effects and function usage are all really blurry IMO. Maybe if you write a function that answers the original problem exactly, using your fisherYates function, it would be clearer.
Alsciende
Answer updated with improved code. Also, @Frerich Raabe: problem with stopping after eight swaps is mentioned.
TheMachineCharmer
Your Fisher-Yates algorithm is wrong. r should depend on i. See my anwser: http://stackoverflow.com/questions/2380019/generate-8-unique-random-numbers-between-1-and-100/2380349#2380349
Alsciende
Ooops sorry my horrible mistake!! Your implementation is cool. Liked it. +1 . Please let me know if anything else is wrong with it.Thanks.
TheMachineCharmer
Yes, it's still wrong :) _i_ should start at _myArray.length_ and decrease. As it is now, your first step picks a random index between 0 and 0. Otherwise it's correct, but you should rename the stop_count parameter (e.g. nb_picks) and return myArray.slice(0, nb_picks) instead of manually creating _result_. Might as well use the standard library, right?
Alsciende
Yes right. Learning a lot from you. :) Please,take a look at it again! Thanks again.
TheMachineCharmer
Well... Now _i_ is initialized with _myArray.length_, right? So you can't read _myArray[i]_ (well you can but it's undefined). Either start at _length-1_ (and change the value of _r_ and stop at 0 instead of 1) or access the element as _myArray[i-1]_. Also, declare _i_ with _var_.
Alsciende
On second thought: if you start at _length-1_, you must stop at 1. But if you start at _length_, you must stop at 2.
Alsciende
@The Machine Charmer: Performing just eight swaps is fine. It's a standard optimization and the chances of ending up with '1,2,3,76,5,6,7,8' are extremely small. Unless you have a bug in your algorithm implementation. ;-)
Frerich Raabe
@Frerich Raabe: There are total 100! and there are 92! permutations with first 8 elements untouched. So there is 92!/100! chance that I end up with [1,2,3,..8] :) Not so good i guess :D
TheMachineCharmer
i=1 must be included in your loop, and r must be Math.floor(Math.random()*(i+1)) to have a random number in [0,i].
Alsciende
+11  A: 
  1. Populate an array with the numbers 1 through 100.
  2. Use this JavaScript code to shuffle it.
  3. Take the first 8 elements of the resulting array.
RegDwight
surely it's more efficient to tweak the code to only do the first 8 shuffles? (and then take the last 8 elements of the semi-shuffled array)
second
+5  A: 
var arr = []
while(arr.length < 8){
  var randomnumber=Math.ceil(Math.random()*100)
  var found=false;
  for(var i=0;i<arr.length;i++){
    if(arr[i]==randomnumber){found=true;break}
  }
  if(!found)arr[arr.length]=randomnumber;
}
adam0101
Actual code is so much better for such questions than pseudocode ;) (deleted my answer that was pseudocode...)
romkyns
O can be picked ; use var randomnumber=Math.ceil(Math.random()*100)
Alsciende
Doesn't work. Firebugs output was "[84, 10, 68, 14, 55, 85, 44, 55]"
dotty
Another test case displayed " [70, 70, 54, 60, 82, 50, 23, 33]"
dotty
Yep, I shouldn't have used "in". Changed the code.
adam0101
Added Alsciende's contribution too
adam0101
Permutation solutions are better because they don't become exponentially worse when the number of elements to pick increase.
Alsciende
This seems a better, and shorter, solution.
dotty
@Alsciende wouldn't it depend on the ratio of numbers being picked to the numbers to pick from? For example, if picking 8 numbers from 1 to 1,000,000 I would think this would work better. If picking 800,000 numbers from 1 to 1,000,000 the Knuth Shuffle would be better suited.
adam0101
Yes, that's totally right. Although the permutation solution is only in O(n), so I think that's a fair price for safe reusability.
Alsciende
You don't need to shuffle the entire array of 1,000,000 numbers. You only need to swap eight numbers, so that the first eight elements are shuffled.
Frerich Raabe
-1: this algorithm is the naive approach; it's very inefficient.
Frerich Raabe
Wow. Naive seems a bit strong. It may not be the best solution, but it's simple, short, easy to see what's going on, and runs within acceptable operating parameters for what needs to be accomplished. On to the next task. Perfection is great, but 'done' is better than 'perfect'.
adam0101
@Frerich: Although I found some inefficiencies (the `for` loop could be replaced by `i=arr.length; while (i--)`), I'd be interested in your solution.
Marcel Korpel
The main issue with adam0101's answer is that you can't build a function out of it. It only works well if the number of picks is vastly inferior to the size of the set.
Alsciende
@Marcel Korpel: The efficiency problems with this solution are on a higher level; the old (like, over 70 years old!) Fisher-Yates algorithm is both shorter and faster. A few of the other answers here use it.
Frerich Raabe
+1  A: 

Same permutation algorithm as The Machine Charmer, but with a prototyped implementation. Better suited to large number of picks. Uses js 1.7 destructuring assignment if available.

// swaps elements at index i and j in array this
// swapping is easy on js 1.7 (feature detection)
Array.prototype.swap = (function () {
    var i=0, j=1;
    try { [i,j]=[j,i]; }
    catch (e) {}
    if(i) {
        return function(i,j) {
            [this[i],this[j]] = [this[j],this[i]];
            return this;
        }
    } else {
        return function(i,j) {
            var temp = this[i];
            this[i] = this[j];
            this[j] = temp;
            return this;
        }
    }
})();


// shuffles array this
Array.prototype.shuffle = function() {
    for(var i=this.length; i>1; i--) {
        this.swap(i-1, Math.floor(i*Math.random()));
    }
    return this;
}

// returns n unique random numbers between min and max
function pick(n, min, max) {
    var a = [], i = max;
    while(i >= min) a.push(i--);
    return a.shuffle().slice(0,n);
}

pick(8,1,100);

Edit: An other proposition, better suited to small number of picks, based on belugabob's answer. To guarantee uniqueness, we remove the picked numbers from the array.

// removes n random elements from array this
// and returns them
Array.prototype.pick = function(n) {
    if(!n || !this.length) return [];
    var i = Math.floor(this.length*Math.random());
    return this.splice(i,1).concat(this.pick(n-1));
}

// returns n unique random numbers between min and max
function pick(n, min, max) {
    var a = [], i = max;
    while(i >= min) a.push(i--);
    return a.pick(n);
}

pick(8,1,100);
Alsciende
Nice recursive implementation - I've posted an alternative, in my answer, that doesn't use splice, as I feel that this is an avoidable performance hit (Not that the OP had any issues with performance)
belugabob
Your solution is clever, but I won't use it in my Array#pick method because I don't want _this_ to have its elements shuffled around when I return it.
Alsciende
Which array do you not want to be shuffled, the original 1-100 array or the results? The former shouldn't matter, as it's a working array, and the latter will, by the nature of the code, come out in a random order anyway. Not quite sure that I understand your reasons.
belugabob
The original one. I implemented a generic Array#pick method, which I find useful. This function doesn't know if _this_ is a working array or not. To be generic, it doesn't alter _this_ more than necessary.
Alsciende
But it still alters it, even if only a little, which is unaviodable when using this technique.
belugabob
Hum it doesn't look like you read my answer at all before commenting... My Array#pick method "removes n random elements from array _this_ and returns them". "removes" being the key word here. Just like Array#splice, Array#pick removes some elements, *but leaves the rest of the array untouched*.
Alsciende
I read your answer just fine - if Array#splice removes elements, then the array has been changed - or am I missing something?
belugabob
I said "it doesn't alter this more than necessary". The necessary part is the goal of the function : "removes n random elements from the array and returns them". The unnecessary part is "elements get shuffled around".
Alsciende
+1  A: 

I would do this:

function randomInt(min, max) {
    return Math.round(min + Math.random()*(max-min));
}
var index = {}, numbers = [];
for (var i=0; i<8; ++i) {
    var number;
    do {
        number = randomInt(1, 100);
    } while (index.hasOwnProperty("_"+number));
    index["_"+number] = true;
    numbers.push(number);
}
delete index;
Gumbo
A: 

How about using object properties as a hash table? This way your best scenario is to only randomize 8 times. It would only be effective if you want a small part of the range of numbers. It's also much less memory intensive than Fisher-Yates because you don't have to allocate space for an array.

var ht={}, i=rands=8;
while ( i>0 || keys(ht).length<rands) ht[Math.ceil(Math.random()*100)]=i--;
alert(keys(ht));

I then found out that Object.keys(obj) is an ECMAScript 5 feature so the above is pretty much useless on the internets right now. Fear not, because I made it ECMAScript 3 compatible by adding a keys function like this.

if (typeof keys == "undefined") 
{ 
  var keys = function(obj) 
  {
    props=[];
    for (k in ht) if (ht.hasOwnProperty(k)) props.push(k);
    return props;
  }
}
Jonas Elfström
A: 

Adam0101's answer is correct. This is a slightly shorter version - the 'found' variable isn't necessary.

I tested this in Firebug.

var arr = [];
while (arr.length < 8){
  var randomnum=Math.ceil(Math.random()*100);
  for(var i=0; i<arr.length; i++){
    if(arr[i]==randomnum){break;}
  }
  arr[arr.length]=randomnum;
}
console.log(arr);
Nathan Long
So you break out of the FOR loop, but still add the number?
adam0101
Tested it and got 32,12,79,44,36,44,76,34. This doesn't work.
adam0101
+4  A: 

To avoid any long and unreliable shuffles, I'd do the following...

  1. Generate an array that contains the number between 1 and 100, in order.
  2. Generate a random number between 1 and 100
  3. Look up the number at this index in the array and store in your results
  4. Remove the elemnt from the array, making it one shorter
  5. Repeat from step 2, but use 99 as the upper limit of the random number
  6. Repeat from step 2, but use 98 as the upper limit of the random number
  7. Repeat from step 2, but use 97 as the upper limit of the random number
  8. Repeat from step 2, but use 96 as the upper limit of the random number
  9. Repeat from step 2, but use 95 as the upper limit of the random number
  10. Repeat from step 2, but use 94 as the upper limit of the random number
  11. Repeat from step 2, but use 93 as the upper limit of the random number

Voila - no repeated numbers.

I may post some actual code later, if anybody is interested.

Edit: It's probably the competitive streak in me but, having seen the post by @Alsciende, I couldn't resist posting the code that I promised.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
<head>
<title>8 unique random number between 1 and 100</title>
<script type="text/javascript" language="Javascript">
    function pick(n, min, max){
        var values = [], i = max;
        while(i >= min) values.push(i--);
        var results = [];
        var maxIndex = max;
        for(i=1; i <= n; i++){
            maxIndex--;
            var index = Math.floor(maxIndex * Math.random());
            results.push(values[index]);
            values[index] = values[maxIndex];
        }
        return results;
    }
    function go(){
        var running = true;
        do{
            if(!confirm(pick(8, 1, 100).sort(function(a,b){return a - b;}))){
                running = false;
            }
        }while(running)
    }
</script>
</head>

<body>
    <h1>8 unique random number between 1 and 100</h1>
    <p><button onclick="go()">Click me</button> to start generating numbers.</p>
    <p>When the numbers appear, click OK to generate another set, or Cancel to stop.</p>
</body>

belugabob
But then your eighth number is random from 1 to 92, not 1 to 100. If you had to pick 90 numbers, your last number would only be picked from 1 to 10, wouldn't it?
adam0101
@adam0101 No, because he removes numbers as he picks them. So in step 5, there are only 99 numbers in his array.@belugabob You are not more efficient than Knuth Shuffle. In fact, the splice is probably more expensive than the shuffle (which is perfectly reliable)
Alsciende
@adam0101: He's removing the chosen element from the array (see step 4 above), thus avoiding that any elements gets chosen twice. He then uses a lower upper bound for the next random number, simply because the array is shorter.
Frerich Raabe
@Alsciende, Yes - thought that there would be a way of doing this more efficiently using a shuffle, but wasn't completely sure. To to avoid deleting the item from the array, simply copy the last entry from the array (providing it wasn't the one that you picked) into the position which you picked from.
belugabob
I see. I read it wrong.
adam0101
See my response for an implementation in js
Alsciende
Ah! recursion is so much more elegant :P But your solution is nice, avoiding the slice. You could make it nicer by decreasing values.length instead of using maxIndex. I prefer my slick Array#pick method, though. (and don't worry about competition, nobody voted me up :( )
Alsciende
The reason for not decrementing values.length, is that there is no guarantee that decreasing the length of an array is not done by reallocating memory. Using maxIndex has the same effect, by simply ignoring the last entries in the array, as they become irrelevant.
belugabob
+2  A: 

Shuffling the numbers from 1 to 100 is the right basic strategy, but if you need only 8 shuffled numbers, there's no need to shuffle all 100 numbers.

I don't know Javascript very well, but I believe it's easy to create an array of 100 nulls quickly. Then, for 8 rounds, you swap the n'th element of the array (n starting at 0) with a randomly selected element from n+1 through 99. Of course, any elements not populated yet mean that the element would really have been the original index plus 1, so that's trivial to factor in. When you're done with the 8 rounds, the first 8 elements of your array will have your 8 shuffled numbers.

Randal Schwartz
A: 
var arr = [];
while(arr.length < 8){
  var randomnumber=Math.ceil(Math.random()*(100 - arr.length)), i;
  for(i=0; i < arr.length; i++) {
    if(arr[i] <= randomnumber) randomnumber++;
    else break;
  }
  arr.splice(i, 0, randomnumber);
}​

In spite of what you might think, incrementing 'randomnumber' doesn't skew any probabilities - it is mathematically equivalent to removing the previous selections from the array.

It's faster than shuffling an array of 100 values, and does not involve 'repicking'.

sje397
[Here](http://jsfiddle.net/5du2A/) is a demo that chooses 8 numbers 100000 times and proves they are always unique, and shows the distribution of chosen numbers in a bar graph (histogram). You need a canvas-capable browser.
sje397
And [here](http://jsfiddle.net/VZKtX/2/) is a graph showing the time taken to generate from 1 to 30 random numbers between 1 and 100 inclusive, 100000 times each, using this method (blue) vs picking randomly from an array (red - note this second method is cheaper than and a component of the shuffle method).
sje397