30

I need to get random number, but it should not be equal to the previous number. Here is my piece of the code. But it doesn't work.

function getNumber(){
  var min = 0;
  var max = 4;
  var i;
  i = Math.floor(Math.random() * (max - min)) + min;
  if (i=== i) {
    i = Math.floor(Math.random() * (max - min)) + min;
  }
  return i;
};

console.log(getNumber());
jamylak
  • 128,818
  • 30
  • 231
  • 230
NJV
  • 309
  • 1
  • 3
  • 4
  • 2
    `i=== i` is always true, so you always execute what's inside of `if`. – Kamil Szot Oct 15 '16 at 07:26
  • ` i=== i` will always return `true` , so the 2nd assignment to `i` will always be executed. Store the previous number in a variable and then compare it to the new number instead of comparing the new number to itself – mrid Oct 15 '16 at 07:27
  • Get an array of required length and shuffle the array.. IMO, It is better to avoid `while` loop every time you ask for a number.. – Rayon Oct 15 '16 at 07:44
  • Is this question says don't repeat just previous number or all previous number? The logic for both will be different. – Pratiyush Kumar Singh Apr 20 '17 at 16:47
  • you need to save the value of the last value of i. It either needs to be done outside the function or use arguments to pass last value. IMO – DraganAscii Apr 20 '17 at 17:36
  • @PratiyushKumarSingh _"it should not be equal to the previous number"_ – guest271314 Apr 21 '17 at 05:48

16 Answers16

24

This answer presents three attempts

  1. A simple version with a property of the function getNumber, last, which stores the last random value.

  2. A version which uses a closure over the min and max values with raising an exception if max is smaller than min.

  3. A version which combines the closure and the idea of keeping all random values and use it as it seems appropriate.


One

You could use a property of getNumber to store the last number and use a do ... while loop.

function getNumber() {
    var min = 0,
        max = 4,
        random;

    do {
        random = Math.floor(Math.random() * (max - min)) + min;
    } while (random === getNumber.last);
    getNumber.last = random;
    return random;
};

var i;
for (i = 0; i < 100; i++) {
    console.log(getNumber());
}
.as-console-wrapper { max-height: 100% !important; top: 0; }

Two

Another proposal with a closure over the interval and the last random value.

function setRandomInterval(min, max) {
    var last;
    if (min >= max) {
        throw 'Selected interval [' + min + ', ' + max + ') does not work for random numbers.';
    }
    return function () {
        var random;
        do {
            random = Math.floor(Math.random() * (max - min)) + min;
        } while (random === last);
        last = random;
        return random;
    };
}

var i,
    getRandom = setRandomInterval(0, 4);

for (i = 0; i < 100; i++) {
    console.log(getRandom());
}

setRandomInterval(4, 4); // throw error
.as-console-wrapper { max-height: 100% !important; top: 0; }

Three

This proposal uses the idea to minimise the call of a new random number. It works with two variables, value for the continuing same random value and count for saving the count of the same value.

The function looks first if the saved count is given and if the value is not equal with the last value. If that happens, the saved value is returned and count is decremented.

Otherwise a new random numner is generated and checked as above (first proposal). If the number is equal to the last value, the count is incremented and it goes on with generating a new random value.

As result, almost all previous generated random values are used.

function setRandomInterval(min, max) {
    var last,      // keeping the last random value
        value,     // value which is repeated selected
        count = 0, // count of repeated value
        getR = function () { return Math.floor(Math.random() * (max - min)) + min; };

    if (min >= max) {
        throw 'Selected interval [' + min + ', ' + max + ') does not work for random numbers.';
    }
    return function () {
        var random;
        if (count && value !== last) {
            --count;
            return last = value;
        }
        random = getR();
        while (random === last) {
            value = random;
            ++count;
            random = getR();
        }
        return last = random;
    };
}

var i,
    getRandom = setRandomInterval(0, 4);

for (i = 0; i < 100; i++) {
    console.log(getRandom());
}
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • 1
    You could substitute using logic at `if` conditions which increments or decrements `i`, `getNumber.last` for `do..while` loop to eliminate need for additional calls to `getNumber`; see also [Random integer in a certain range excluding one number](http://stackoverflow.com/questions/34182699/random-integer-in-a-certain-range-excluding-one-number/) – guest271314 Oct 18 '16 at 02:53
  • i don't get you. which if condition do you mean (there is no)? – Nina Scholz Oct 18 '16 at 06:49
  • The current implementation calls `getNumber` more than 100 times http://plnkr.co/edit/3GHnnhbkf5F8f0fRx9mX?p=preview . You can remove loop, use logic conditions at `if` statements to increment or decrement `i` and `getNumber.last` to call `getNumber` equal to `for` loop calls to return expected result http://plnkr.co/edit/3GHnnhbkf5F8f0fRx9mX?p=preview – guest271314 Oct 18 '16 at 06:54
  • oh i think, i get it. the last for loop is only for demo purpose, and `getNumber` is only called 100 times, not more, because you count the do loop, too. – Nina Scholz Oct 18 '16 at 07:00
  • Referring to `do..while` loop, which could call `getNumber` more than one time to return value that is not identical to first returned value. – guest271314 Oct 18 '16 at 07:08
  • 3
    i am calling only a property of `getNumber`, `getNumber.last`, not the function again. – Nina Scholz Oct 18 '16 at 07:10
  • Careful with the min/max values. You've got an endless loop if someone accidentally sets both to the same number. – Viliam Aboši Apr 18 '17 at 19:41
  • @ViliamAboši, please see version 2 and 3. – Nina Scholz Apr 21 '17 at 21:22
  • @NinaScholz While not officially a requirement of Question or bounty, did not explicitly describe this portion, Version 3 still requires more than 100 calls, including `while` loop, to get 100 numbers which are not repeated? – guest271314 Apr 22 '17 at 14:28
  • Here you prevent last value repeat. But number before last is repeated – Rafiqul Islam Apr 25 '17 at 03:28
  • @Rafiq, i see no evidence for that. – Nina Scholz Apr 25 '17 at 05:52
15

The following method generates a new random number in the [min, max] range and makes sure that this number differs from the previous one, without looping and without recursive calls (Math.random() is called only once):

  • If a previous number exists, decrease max by one
  • Generate a new random number in the range
  • If the new number is equal to or greater than the previous one, add one
    (An alternative: If the new number is equal to the previous one, set it to max + 1)

In order to keep the previous number in a closure, getNumber can be created in an IIFE:

// getNumber generates a different random number in the inclusive range [0, 4]
var getNumber = (function() {
  var previous = NaN;
  return function() {
    var min = 0;
    var max = 4 + (!isNaN(previous) ? -1 : 0);
    var value = Math.floor(Math.random() * (max - min + 1)) + min;
    if (value >= previous) {
      value += 1;
    }
    previous = value;
    return value;
  };
})();

// Test: generate 100 numbers
for (var i = 0; i < 100; i++) {
  console.log(getNumber());
}
.as-console-wrapper {
  max-height: 100% !important;
  top: 0;
}

The [min, max] range is made inclusive by adding 1 to max - min in the following statement:

var value = Math.floor(Math.random() * (max - min + 1)) + min;

This is not a requirement in the question but it feels more natural to me to use an inclusive range.

Community
  • 1
  • 1
ConnorsFan
  • 70,558
  • 13
  • 122
  • 146
  • I don't think that this code i.e. providing a function that accept `min` and `max` as parameters is really safe. If your function accepts parameters, it means you expect several consumers. And in such case different consumers will mess up state (`previous`) for each other and thus make it possible to generate two consecuitive equal values for the same consumer. I think the simplest safe way is to make a factory function `createGetNumber` that will accept `min` and `max` and return a function with no parameters which is uniquely bound to a state. – SergGr Apr 16 '17 at 18:56
  • @SergGr - Thanks for the comment. I can put back `min` and `max` inside of the function, since providing the ability to change these limits was not required. – ConnorsFan Apr 16 '17 at 19:02
  • 1
    @ConnorsFan, you might want to explicitly state that your approach guarantees that a number is randomly drawn from the set of numbers excluding the previous number, even in the case when the drawn number is the previous. As it now stands, it may be misunderstood as a simple adjustment that sacrifices randomness. – Tomas Langkaas Apr 18 '17 at 22:10
  • @TomasLangkaas - Thanks for the advice. I rephrased my answer, hoping that it is clearer now. – ConnorsFan Apr 19 '17 at 00:33
6

First of all function should compare with previous value, now We have only i variable which is compared to itself. To be sure that we not have previous value we need to do loop inside ( recursive in my solution ), because single if statement not give us sure that second random will be not the same ( exists chance on that ). Your number set is very small so chance for collision is high and it is possible that loop needs few executions.

function getNumber(prev){
  var min = 0;
  var max = 4;
  var next;
  
  next = Math.floor(Math.random() * (max - min)) + min;
  
  if (next===prev) {
    console.log("--run recursion. Our next is ="+next); //log only for test case
    next = getNumber(prev); //recursive
  }
  
  return next;
};

//test 100 times
var num=0;
for ( var i=0; i<100; i++){
  num=getNumber(num);
  console.log(num);
}

As You can see in tests we never have two the same values next to each other. I also added some console.log to show how many times recursion needs to run to find next number which is different then previous one.

Maciej Sikora
  • 19,374
  • 4
  • 49
  • 50
5

A general solution

Keep track of the last generated number. When generating a new number, check that it differs from the last one. If not, keep generating new numbers until it is different, then output it.

Working demo

var getNumber = (function(){
  var min = 0;
  var max = 4;
  var last = -1;
  return function(){
    var current;
    do{
      // draw a random number from the range [min, max]
      current = Math.floor(Math.random() * (max + 1 - min)) + min;
    } while(current === last)
    return (last = current);
  }
})();

// generate a sequence of 100 numbers,
// see that they all differ from the last

for(var test = [], i = 0; i < 100; i++){
  test[i] = getNumber();
}
console.log(test);

Comment about computational efficiency

As discussed in comments and other answers, a potential drawback of the approach above is that it may require several attempts at generating a random number if the generated number equals the previous. Note that the probability of needing many attempts is quite low (it follows a rapidly declining geometric distribution). For practical purposes, this is not likely to have any noticeable impact.

However, it is possible to avoid making several attempts at generating a new random number by directly drawing a random number from the set of numbers in the range [min, max] excluding the previously drawn number: This is well demonstrated in the answer by @ConnorsFan, where only one random number is generated at each function call, while randomness is still preserved.

Community
  • 1
  • 1
Tomas Langkaas
  • 4,551
  • 2
  • 19
  • 34
  • How many calls to `getNumber` are made to generate 100 numbers? – guest271314 Apr 17 '17 at 22:24
  • `getNumber` is called once for each number, but the `do/while` loop could in theory run for a while. In reality, it most often runs only once. With 5 possible numbers, about 1 in 5 times a repetition is needed to get a different number. – Tomas Langkaas Apr 17 '17 at 22:29
  • That is not a concern at OP, though a side-effect noticed at several Answers, including this users' first Answer http://stackoverflow.com/a/40057625/. Ideally, only 100 calls to a function or loop should be necessary to meet requirement, for example, see http://stackoverflow.com/a/40102421/ – guest271314 Apr 17 '17 at 22:32
  • 2
    @guest271314, I did not get that requirement. In that case, ConnorsFan seems to get it right. – Tomas Langkaas Apr 17 '17 at 23:09
  • You are correct. Did not specify as part of requirement of bounty. Noted at comment some time ago at http://stackoverflow.com/questions/40056297/random-number-which-is-not-equal-to-the-previous-number/43460569?noredirect=1#comment67469961_40056421. Votes at large will determine the present bounty http://stackoverflow.com/questions/40056297/random-number-which-is-not-equal-to-the-previous-number/43460569?noredirect=1#comment73978036_40056297 – guest271314 Apr 17 '17 at 23:11
4

You'll need a variable with a greater scope than the variables local to your getNumber function. Try:

var j;
function getNumber(){
  var min = 0;
  var max = 4;
  var i = Math.floor(Math.random() * (max - min)) + min;
  if (j === i) {
    i = getNumber();
  }
  j = i;
  return i;
};
tewathia
  • 6,890
  • 3
  • 22
  • 27
4

Remove the previous value from the set of possible values right from the start.

function getNumber(previous) {
  var numbers = [0, 1, 2, 3, 4];
  if (previous !== undefined) {
      numbers.splice(numbers.indexOf(previous), 1);
  }

  var min = 0;
  var max = numbers.length;
  var i;
  i = Math.floor(Math.random() * (max - min)) + min;

  return numbers[i];
};

//demonstration. No 2 in  a row the same
var random;
for (var i = 0; i < 100; i++) {
  random = getNumber(random);
  console.log(random);
}
theTaoOfJS
  • 206
  • 2
  • 3
3

You can use an implementation of @NinaScholz pattern, where the previous value is stored as property of the calling function, substituting conditional logic to increment or decrement current return value for a loop.

If the current value is equal to the previously returned value, the current value is changed during the current function call, without using a loop or recursion, before returning the changed value.

var t = 0;

function getNumber() {
  var min = 0,
    max = 4,
    i = Math.floor(Math.random() * (max - min)) + min;

  console.log(`getNumber calls: ${++t}, i: ${i}, this.j: ${this.j}`);

  if (isNaN(this.j) || this.j != i) {
    this.j = i;
    return this.j
  } else {

    if (this.j === i) {

      if (i - 1 < min || i + 1 < max) {
        this.j = i + 1;
        return this.j
      }

      if (i + 1 >= max || i - 1 === min) {
        this.j = i - 1;
        return this.j
      }

      this.j = Math.random() < Math.random() ? --i : ++i;
      return this.j

    }
  }
};

for (var len = 0; len < 100; len++) {
  console.log("random number: ", getNumber());
}
guest271314
  • 1
  • 15
  • 104
  • 177
2

This solution uses ES6 generators and avoids generating random numbers until you find one that complies with the precondition (two correlated numbers must be different).

The main idea is to have an array with the numbers and an array with indexes. You then get a random index (to comply with the precondition, the indexes' array will be the result of filtering the array of indexes with the previous selected index). The return value will be the number that correspond to the index in the numbers' array.

function* genNumber(max = 4) {// Assuming non-repeating values from 0 to max
  let values = [...Array(max).keys()],
      indexes = [...Array(max).keys()],
      lastIndex,
      validIndexes;

  do {
    validIndexes = indexes.filter((x) => x !== lastIndex);
    lastIndex = validIndexes[Math.floor(Math.random() * validIndexes.length)];
    yield values[lastIndex];
  } while(true);
}

var gen = genNumber();
for(var i = 0; i < 100; i++) {
  console.log(gen.next().value);
}

Here's the fiddle in case you want to check the result.

acontell
  • 6,792
  • 1
  • 19
  • 32
2

Save the previous generated random number in a array check the new number with the existing number you can prevent duplicate random number generation.

// global variables
 tot_num = 10; // How many number want to generate?
 minimum = 0; // Lower limit
 maximum = 4; // upper limit
 gen_rand_numbers = []; // Store generated random number to prevent duplicate.

/*********** **This Function check duplicate number** ****************/
 function in_array(array, el) {
 for (var i = 0; i < array.length; i++) {
  if (array[i] == el) {
   return true;
  }
 }
 return false;
}

/*--- This Function generate Random Number ---*/
function getNumber(minimum, maximum) {
 var rand = Math.floor(Math.random() * (maximum - minimum + 1)) + minimum;
 if (gen_rand_numbers.length <= (maximum - minimum)) {
  if (!in_array(gen_rand_numbers, rand)) {
   gen_rand_numbers.push(rand);
   //alert(rand)
      console.log(rand);
   return rand;
  } else {
   return getNumber(minimum, maximum);
  }
 } else {
  alert('Final Random Number: ' + gen_rand_numbers);
 }

}

/*--- This Function call random number generator to get more than one random number ---*/

function how_many(tot_num) {
 for (var j = 0; j < tot_num; j++) {
  getNumber(minimum, maximum);
 }
 
}
<script src = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js" > </script>

 <input type = "button" onclick = "how_many(4)" value = "Random Number" >
Rafiqul Islam
  • 1,636
  • 1
  • 12
  • 25
1

You can use a augmented implementation of a linear congruential generator.

A linear congruential generator (LCG) is an algorithm that yields a sequence of pseudo-randomized numbers calculated with a discontinuous piecewise linear equation.

The following function returns a seeded random number in conjunction with a min and max value:

Math.seededRandom = function(seed, min, max) {
  max = max || 1;
  min = min || 0;

  // remove this for normal seeded randomization
  seed *= Math.random() * max;

  seed = (seed * 9301 + 49297) % 233280;
  let rnd = seed / 233280.0;

  return min + rnd * (max - min);
};

In your case, because you never want the new number to be the same as the previous, then you can pass the previously generated number as the seed.

Here is an example of this follows which generates 100 random numbers:

Math.seededRandom = function(seed, min, max) {
  max = max || 1;
  min = min || 0;
  
  // remove this for normal seeded randomization
  seed *= Math.random() * max;
  
  seed = (seed * 9301 + 49297) % 233280;
  let rnd = seed / 233280.0;

  return min + rnd * (max - min);
};



let count = 0;
let randomNumbers = [];
let max = 10;
do {
  let seed = (randomNumbers[randomNumbers.length -1] || Math.random() * max);
  
  randomNumbers.push(Math.seededRandom(seed, 0, max));
  count++;
} while (count < 100)

console.log(randomNumbers);
Zze
  • 18,229
  • 13
  • 85
  • 118
  • The original Question considered `0,1,2,3`, or whole numbers. Current Answer returns duplicate while number in succession, though decimal portion is unique `3:4.5591053733292854,:4 :4.849922675644411`, `7:3.9133924461395226,8:3.023985816875585` – guest271314 Apr 19 '17 at 01:41
1

A fun answer, to generate numbers from 0 to 4 in one line:

console.log(Math.random().toString(5).substring(2).replace(/(.)\1+/g, '$1').split('').map(Number));

Explanation:

Math.random() //generate a random number
    .toString(5) //change the number to string, use only 5 characters for it (0, 1, 2, 3, 4)
    .substring(2) //get rid of '0.'
    .replace(/(.)\1+/g, '$1') //remove duplicates
    .split('') //change string to array
    .map(Number) //cast chars into numbers

And a longer version with generator:

let Gen = function* () {
  const generateString = (str) => str.concat(Math.random().toString(5).substring(2)).replace(/(.)\1+/g, '$1');
  let str = generateString('');
  let set = str.split('').map(Number);
  
  while (true) {
    if (set.length === 0) {
      str = generateString(str).substring(str.length);
      set = str.split('').map(Number);
    }
    yield set.pop();
  }
}

let gen = Gen();

console.log(gen.next().value);
console.log(gen.next().value);
console.log(gen.next().value);
console.log(gen.next().value);
console.log(gen.next().value);
console.log(gen.next().value);
console.log(gen.next().value);
console.log(gen.next().value);
console.log(gen.next().value);
console.log(gen.next().value);
console.log(gen.next().value);
Oskar
  • 2,548
  • 1
  • 20
  • 22
1
function getNumber(){
    var min = 0;
    var max = 4;
    var i;
    i = Math.floor(Math.random() * (max - min)) + min;
    while(i==getNumber.last)
        i = Math.floor(Math.random() * (max - min)) + min;
    getNumber.last=i;
    return i;
};

console.log(getNumber());
Ionuț
  • 55
  • 1
  • 1
  • 9
0

Try this

var prev_no = -10;

function getNumber(){
    var min = 0;
    var max = 4;
    var i;
    i = Math.floor(Math.random() * (max - min)) + min;
    while (i == prev_no) {
        i = Math.floor(Math.random() * (max - min)) + min;
        prev_no = i;
    }

    return i;
};

console.log(getNumber());
mrid
  • 5,782
  • 5
  • 28
  • 71
  • The initialized value of your `prev_no` is part of the min-max range for the random variable. So in the case in which the first generated random number is 0, you'll get an incorrect result – tewathia Oct 15 '16 at 07:34
  • It is still a number, might need to be changed everytime the min/max values are modified. Also, shouldn't the `prev_no=i` line be outside the while loop? – tewathia Oct 15 '16 at 07:49
  • You function mutate outside scope variable, it is not pure function. – Maciej Sikora Oct 15 '16 at 07:50
  • 1
    @MaciejSikora, a "pure function" with no input arguments by definition can't produce different outputs. Although I agree that putting `prev_no` inside some local scope would be better. But if this code is a part of some module that is already wrapped into a scope, I think this is acceptable. – SergGr Apr 16 '17 at 18:51
0

You can use Promise, Array.prototype.forEach(), setTimeout. Create and iterate an array having .length set to max; use setTimeout within .forEach() callback with duration set to a random value to push the index of the array to a new array for non-uniform distribution of of the indexes within the new array. Return resolved Promise from getNumber function where Promise value at .then() will be an array of .length max having random index from .forEach() callback as values without duplicate entries.

function getNumber(max) {
  this.max = max;
  this.keys = [];
  this.arr = Array.from(Array(this.max));
  this.resolver = function getRandom(resolve) {
    this.arr.forEach(function each(_, index) {
      setTimeout(function timeout(g) {
        g.keys.push(index);
        if (g.keys.length === g.max) {
          resolve(g.keys)
        };
      }, Math.random() * Math.PI * 100, this);
    }, this)
  };
  this.promise = new Promise(this.resolver.bind(this));
}

var pre = document.querySelector("pre");

var random1 = new getNumber(4);
random1.promise.then(function(keys) {
  pre.textContent += keys.length + ":\n";
  keys.forEach(function(key) {
    pre.textContent += key + " ";
  })
});

var random2 = new getNumber(1000);
random2.promise.then(function(keys) {
  pre.textContent += "\n\n";
  pre.textContent += keys.length + ":\n";
  keys.forEach(function(key) {
    pre.textContent += key + " ";
  })
});
pre {
  white-space: pre-wrap;
  width: 75vw;
}
<pre></pre>
guest271314
  • 1
  • 15
  • 104
  • 177
0

I'm surprised no one has suggested a simple solution like this:

function getRandomNum(min, max, exclude) {
    if (Number.isNaN(exclude)) exclude = null;

    let randomNum = null;

    do {
        randomNum = Math.floor(min + Math.random() * (max + 1 - min));
    } while (randomNum === exclude);

    return randomNum;
}

Note that "exclude" is optional. You would use it like this:

// Pick 2 unique random numbers between 1 and 10
let firstNum = getRandomNum(1, 10);
let secondNum = getRandomNum(1, 10, firstNum);

You can give it a try here:

function getRandomNum(min, max, exclude) {
  if (Number.isNaN(exclude)) exclude = null;

  let randomNum = null;

  do {
    randomNum = Math.floor(min + Math.random() * (max + 1 - min));
  } while (randomNum === exclude);

  return randomNum;
}


// Pick 2 unique random numbers between 1 and 10
let firstNum = getRandomNum(1, 10);
let secondNum = getRandomNum(1, 10, firstNum);


// Output the numbers
document.write(firstNum + ' and ' + secondNum);
gavanon
  • 1,293
  • 14
  • 15
-2

You can't achieve this unless you you do a database query to check existence of the new number. If existing, repeat the process.

There is an architectural possibility of making unique random number is to generate two random number and combine the strings.

For example:

rand_num1 = rand(5);
rand_num2 = rand(4);

then combine rand_num1 and rand_num2 which is more like unique

Practical example:

(23456)(2345)
(23458)(1290)
(12345)(2345)

Also increase the number of digits to reduce repetition.

Alexan
  • 8,165
  • 14
  • 74
  • 101
SEM
  • 9
  • 4
  • aren't you overcomplicating things by doing DB queries. I think, using static variable would result in the similar output. – computnik Apr 23 '17 at 05:03
  • 1000s of random numbers issued already, but now how do you check the uniqueness without storing the previously created numbers? – SEM Apr 23 '17 at 15:31