14

I need to generate two different random numbers, they can't be equal to each other or to a third number. I tried to use a lot of if's to cover every possibility but, it seems my algorithm skills are not that good.

Can anyone help me on this?

var numberOne = Math.floor(Math.random() * 4);
var numberTwo = Math.floor(Math.random() * 4);
var numberThree = 3; // This number will not always be 3

if((numberOne == numberThree) && (numberOne + 1 < 3)) {
    numberOne++;
} else if ((numberOne == numberThree) && (numberOne + 1 == 3)) {
    numberOne = 0;
}

if ((numberOne == numberTwo) && (numberOne+1 < 3)) {
    if (numberOne+1 < 3) {
        numberOne++;
    } else if(numberThree != 0) {
        numberOne = 0;
    }
}

This is what I have so far, the next step would be:

if (numberTwo == numberThree) {
    (...)
}

Is my line of thought right? Note: Numbers generated need to be between 0 and 3. Thanks in advance.

Aanchal1103
  • 917
  • 8
  • 21
Francisco Costa
  • 379
  • 1
  • 3
  • 22
  • In what range should that number be? If it's relatively small: populate an array with all numbers in that range, shuffle it, take the first two. – deceze Aug 21 '14 at 11:03
  • 2
    btw, make sure you use a real shuffling algorithm like the knuth shuffle. Dont use the "sort with random as a comparator" algorithm because that leads to biased results. – hugomg Aug 21 '14 at 11:15
  • What's this for? How *good* (from a statistical point of view) does the randomness need to be? – Bathsheba Aug 21 '14 at 11:15
  • Forget about computers, *think*. Suppose you have two dice, and you're asked for two random numbers between 1 and 6, but they're not allowed to equal each other or the number 4. How would you do that? – Colonel Panic Aug 26 '14 at 13:09

9 Answers9

9

You can run a while loop until all numbers are different.

// All numbers are equal
var numberOne = 3; 
var numberTwo = 3; 
var numberThree = 3; 

// run this loop until numberOne is different than numberThree
do {
    numberOne = Math.floor(Math.random() * 4);
} while(numberOne === numberThree);

// run this loop until numberTwo is different than numberThree and numberOne
do {
    numberTwo = Math.floor(Math.random() * 4);
} while(numberTwo === numberThree || numberTwo === numberOne);

Here is the jsfiddle with the above code based on @jfriend00's suggestion http://jsfiddle.net/x4g4kkwc/1.

Here is the original working demo: http://jsfiddle.net/x4g4kkwc/

Tasos K.
  • 7,979
  • 7
  • 39
  • 63
  • 3
    You really ought to DRY this up some. Why not at least use a `do {} while()` loop instead of the plain `while()` and remove two of the four copies of `Math.floor(Math.random() * 4)` – jfriend00 Aug 21 '14 at 11:19
  • Good point, this reduces code size. Here is the jsfiddle updated with @jfriend00 suggestion http://jsfiddle.net/x4g4kkwc/1/. – Tasos K. Aug 21 '14 at 11:23
  • @TasosK. - you can use the Edit button to improve the code in your answer since that is what most people will see. – jfriend00 Aug 21 '14 at 11:26
  • I am not sure if it is a good idea to edit an answer since it is already been accepted so I added the updated jsfiddle. Hope this is ok. – Tasos K. Aug 21 '14 at 11:28
  • Its perfectly acceptable to edit an accepted answeer, specially in this case where its a straight improvement and not a radical change in your opinion. – hugomg Aug 21 '14 at 11:34
  • In fact, you are encouraged to "improve" your answer at any time. I am still editing/improving a few popular answers I made a couple years ago. As long as you're making the answer better, there's no issue with the fact that it's already accepted. Glad you incorporated the suggestion. – jfriend00 Aug 21 '14 at 21:37
4

You can create an array of random possibilities and then remove items from that array as they are used, selecting future random numbers from the remaining values in the array. This avoids looping trying to find a value that doesn't match previous items.

function makeRandoms(notThis) {
    var randoms = [0,1,2,3];

    // faster way to remove an array item when you don't care about array order
    function removeArrayItem(i) {
        var val = randoms.pop();
        if (i < randoms.length) {
            randoms[i] = val;
        }
    }

    function makeRandom() {
        var rand = randoms[Math.floor(Math.random() * randoms.length)];
        removeArrayItem(rand);
        return rand;
    }

    // remove the notThis item from the array
    if (notThis < randoms.length) {
        removeArrayItem(notThis);
    }

    return {r1: makeRandom(), r2: makeRandom()};
}

Working demo: http://jsfiddle.net/jfriend00/vhy6jxja/

FYI, this technique is generally more efficient than looping until you get something new when you are asking to randomly select most of the numbers within a range because this just eliminates previously used numbers from the random set so it doesn't have to keep guessing over and over until it gets an unused value.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • A place for possible optimization is not to use `randoms.splice(rand,1)` (it's O(n)), but instead `var last = randoms.pop(); if (rand < randoms.length) randoms[rand] = last` which is O(1). –  Aug 21 '14 at 17:56
  • @herby - interesting optimization when you don't care about the array order. I've switched over to that as it is indeed a lot faster. – jfriend00 Aug 21 '14 at 21:05
4

This version minimizes the number of calls to random like you did, but is a bit simpler and not biased. In your version, there is a 2/4 chance that numberOne goes to 0, and a 1/4 chance if goes to 1 and 2. In my version there are equal odds of numberOne ending up as 0, 1 or 2).

i0 = Math.floor(Math.random() * 4); //one of the 4 numbers in [0, 4), namely 3
i1 = Math.floor(Math.random() * 3); //only 3 possibilities left now
i2 = Math.floor(Math.random() * 2); //only two possibilities left now

x0 = i0;
x1 = i1 + (i1 >= i0 ? 1 : 0);
x2 = i2 + (i2 >= i0 ? 1 : 0) + (i2 >= i1 ? 1 : 0);

Its a special case of the array-shuffling version deceze mentioned but for when you have only two numbers

N3R4ZZuRR0
  • 2,400
  • 4
  • 18
  • 32
hugomg
  • 68,213
  • 24
  • 160
  • 246
  • `floor` and `random` don't exist by themselves in Javascript. They are part of the `Math` object. – jfriend00 Aug 21 '14 at 11:17
  • Originally I had left the Math out to keep things simple but on second though I don't think saving those extra characters was really worth it. – hugomg Aug 21 '14 at 11:20
  • @herby: I think it should be fixed now, but looks different than your version to keep it original :) – hugomg Aug 21 '14 at 11:32
1

I'm not sure of what you're trying to do (or actually, why is your code so complicated for what I understood). It might not be the most optimized code ever, but here is my try :

var n3 = 3;
var n2 = Math.floor(Math.random() * 4);
var n1 = Math.floor(Math.random() * 4);

while(n1 == n3)
{
    n1 = Math.floor(Math.random() * 4);
}
while (n2 == n1 || n2 == n3)
{
    n2 = Math.floor(Math.random() * 4);
}

EDIT : Damn, too late ^^

ChoiBedal
  • 111
  • 7
1
var n = 4; //to get two random numbers between 0 and 3
var n3 = 2; //for example
var n1 = Math.floor(Math.random(n-1));
var n2 = Math.floor(Math.random(n-2));
if(n1 >= n3) {
    n1++;
    if(n2 >= n3)
        n2++;
    if(n2 >= n1)
        n2++;
} else {
    if(n2 >= n1)
        n2++;
    if(n2 >= n3)
        n2++;
}

You need to compare n2 with the minimum of n1 and n3 first to ensure you do not have an equality:

Suppose n1=1 and n3=2. If you get n2=1 and compare it first with n3, you won't increase n2 in the first step. In the second step, you would increase it since n2 >= n1. In the end, n2 = 2 = n3.

This algorithm guarantees to have a uniform distribution, and you only call twice Math.random().

R2B2
  • 1,541
  • 1
  • 12
  • 19
1
var rangeTo = 4;
var uniqueID = (function () {
    var id, cache = [];
    return function () {
        id = Math.floor((Math.random() * (new Date).getTime()) % rangeTo);
        var cacheLength = cache.length;
        if (cacheLength === rangeTo) {
            throw new Error("max random error");
        };
        var i = 0
        while (i < cacheLength) {
            if (cache[i] === id) {
                i = 0;
                id = Math.floor((Math.random() * (new Date).getTime()) % rangeTo);
            }
            else {
                i++;
            }
        }
        cache.push(id);
        return id;
    };
})();
Greck
  • 170
  • 1
  • 7
  • I hope that no one else dares to write like Yogi Bear: "This is absolutely awful." without a clear explanation of what the problem is!!! THANX!!! – Greck Aug 21 '14 at 12:16
  • I want to note that the use of while can become a problem of indeterminate execution time. – Greck Aug 21 '14 at 12:23
1

ES 6 Version:

This is basically a function like already mentioned above, but using the an Arrow function and the Spread operator

const uniqueRandom = (...compareNumbers) => {
    let uniqueNumber;
    do {
        uniqueNumber = Math.floor(Math.random() * 4);
    } while(compareNumbers.includes(uniqueNumber));
    return uniqueNumber;
};

const numberOne = uniqueRandom();
const numberTwo = uniqueRandom(numberOne);
const numberThree = uniqueRandom(numberOne, numberTwo);

console.log(numberOne, numberTwo, numberThree);
Ling Vu
  • 4,740
  • 5
  • 24
  • 45
0

Be aware that back-to-back calls to Math.random() triggers a bug in chrome as indicated here, so modify any of the other answers by calling safeRand() below.:

function safeRand() {
  Math.random();
  return Math.random();
}

This still isn't ideal, but reduces the correlations significantly, as every additional, discarded call to Math.random() will.

Community
  • 1
  • 1
jimm101
  • 948
  • 1
  • 14
  • 36
-1

Generally, in pseudo-code, I do :

var nbr1 = random()
var nbr2 = random()
while (nbr1 == nbr2) {
    nbr2 = random();
}

This way you'll get two different random numbers. With an additional condition you can make them different to another (3rd) number.

Adriweb
  • 227
  • 3
  • 17