How can I generate a random number within a range but exclude some?

27,647

Solution 1

Set an array with all the values (this is only a valid option if you're only doing small numbers, like the 25 in your example), like this:

var array = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24];

then, pick a random number between 0 and the array length:

var num = Math.floor(Math.random() * array.length);

remove that index number from the array:

var roll = array.splice(num, 1);

Javascript splice() removes indexed items from an array and returns the item(s) as an array. Perfect for your use.

Grab the first index from the roll, since we only cut 1 out anyway:

var yourNumber = roll[ 0 ];

Keep doing for as many rolls as you want. Also, you might want to store the original array as a copy so that you can "reset" the numbers easily.

Solution 2

This is easy guys. You do not want recursion for this one. These answers are really bad. Ideally you do not want to hardcode the array, either.

function getRandomWithOneExclusion(lengthOfArray,indexToExclude){

  var rand = null;  //an integer

    while(rand === null || rand === indexToExclude){
       rand = Math.round(Math.random() * (lengthOfArray - 1));
    }

  return rand;
}

now use the value returned from the above function to choose an element from whatever array you want, just like so:

var arr = [];
var random = getRandomWithOneExclusion(arr.length,5);  //array has length x, we want to exclude the 5th element
var elem = arr[random];

that's it. if you wanted to exclude more than value, then you would have to make this more sophisticated, but for excluding one value, this works well. A recursive solution for this is overkill and a bad idea.

I haven't tested this, but to exclude more than one element, try this:

function getRandomWithManyExclusions(originalArray,arrayOfIndexesToExclude){

   var rand = null;

   while(rand === null || arrayOfIndexesToExclude.includes(rand)){
         rand = Math.round(Math.random() * (originalArray.length - 1));
    }
     return rand;
  }

The above method does not sound too different from the OP's original method. This method works properly because it does not sample in a biased way from the array.

Solution 3

Suppose you need to choose a random number from the range 1...5 and exclude the values 2, 4 then:

  • Pick a random number from the range 1...3
  • Sort excluded number list
  • For each excluded number less than/equal to the random number: add one to the random number

function getRandomExcept(min, max, except) {
  except.sort(function(a, b) {
    return a - b;
  });
  var random = Math.floor(Math.random() * (max - min + 1 - except.length)) + min;
  var i;
  for (i = 0; i < except.length; i++) {
    if (except[i] > random) {
      break;
    }
    random++;
  }
  return random;
}

/*
 * Test iterations. Make sure that:
 * excluded numbers are skipped 
 * numbers are equally distributed
 */
(function(min, max, except) {
  var iterations = 1000000;
  var i;
  var random;
  var results = {};
  for (i = 0; i < iterations; i++) {
    random = getRandomExcept(min, max, except);
    results[random] = (results[random] || 0) + 1;
  }
  for (random in results) {
    console.log("value: " + random + ", count: " + results[random] + ", percent: " + results[random] * 100 / iterations + "%");
  }
})(1, 5, [2, 4]);

Solution 4

This is example without recursion and without creating a huge array:

const getRandomWithExclude = (min, max, excludeArray) => {
  const randomNumber = Math.floor(Math.random() * (max - min + 1 - excludeArray.length)) + min;
  return randomNumber + excludeArray.sort((a, b) => a - b).reduce((acc, element) => { return randomNumber >= element - acc ? acc + 1 : acc}, 0);
}

const min = 1;
const max = 10;
const excludeArray = [8,2,5];
const result = getRandomWithExclude(min, max, excludeArray);

Solution 5

Hmz :-? Fastest way to randomly get items from an array and ensure they're all unique would be:

var array = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24];

Array.prototype.shuffle = function shuffle(){
    var tempSlot;
    var randomNumber;
    for(var i =0; i != this.length; i++){
        randomNumber = Math.floor(Math.random() * this.length);
        tempSlot = this[i]; 
        this[i] = this[randomNumber]; 
        this[randomNumber] = tempSlot;
    }
}

while(array.length!=0){
    array.shuffle();
    alert(array.pop());    
}
Share:
27,647
Pete
Author by

Pete

There's always something new to learn!

Updated on July 09, 2022

Comments

  • Pete
    Pete almost 2 years

    Basically I pick a random number between 0-24:

    Math.floor(Math.random() * myArray.length); // myArray contains 25 items
    

    Lets say it comes out to be 8. Now I want to get another number in the same range 0-24 but this time, I do not want an 8. The next time, I might roll a 15. Now I want to roll again but I don't want an 8 or 15. The way I am handling this now is by using do while loops and if the number comes out the same, I just reroll.

    This is a small portion of my homework and I, in fact, have it working to meet all the requirements so I guess you could say this is for my own personal benefit so I can write this properly and not end up on "the daily wtf".

  • rockerest
    rockerest about 13 years
    prototype function, shuffle entire array on every loop...I have no evidence, but I am highly skeptical that this is the "fastest way."
  • Khez
    Khez about 13 years
    Well if you wanna be technical about it, a re-shuffle is not necessary. "Fastest" was meant in regards to implementation not processing speed :-?
  • Pete
    Pete about 13 years
    Thanks. You do have an error I believe because you are using floor you don't need to subtract 1 from the length. If I'm wrong let me know because I have some code to fix =P
  • rockerest
    rockerest about 13 years
    @Pete, you could be right. I'm a little foggy on the returns from Math.random(). I subtracted 1 on the chance that Math.random() ever returned "1". If it never does, then, yes: subtracting one will introduce a bug.
  • Pete
    Pete about 13 years
    @rockerest Yeah, the definition is that it returns a number BETWEEN 0-1 so I guess it's implied that it can never be 0 OR 1 exactly.
  • rockerest
    rockerest about 13 years
    @Pete looks like you're right. The max value for Math.random() is something less than 1. I have looked high and low, and the best answer for WHAT that number is, is "less than 1." Why is it such a big secret? Anyway, I've rolled the answer back to my initial response.
  • monsur
    monsur about 13 years
    This is the solution proposed for the same problem in Jon Bentley's Programming Pearls.
  • rockerest
    rockerest about 13 years
    @Pete, my head just exploded.
  • Pete
    Pete about 13 years
    Can't ever have enough random, that's for sure =p
  • Matthew Crumley
    Matthew Crumley about 13 years
    @Pete: That's close, but it can be zero. Specifically Math.random() returns a number in the range, [0, 1). There's no exact upper bound (probably because the exact algorithm used is up to the implementation) but you can basically assume it's the next floating point number below 1.
  • Alexander Mills
    Alexander Mills over 7 years
    I am curious if my answer is correct or if I missing something in the question, because mine, after all this time, seems more scalable/generic.
  • Kody R.
    Kody R. over 7 years
    a while loop is definitely the way to go here
  • Alexander Mills
    Alexander Mills over 7 years
    Did you like the while loop, loopback and make an upvote
  • everlasto
    everlasto over 6 years
    Not convinced if this is an optimal approach. What if you get same random number that is in the exlusion list for a long time?
  • Alexander Mills
    Alexander Mills almost 5 years
    @everlasto the random number generator distribution is smooth/uniform, so there is probably no way to avoid that problem unless you could get a random distribution of numbers that has non-linear holes, like the one we are creating by hand
  • Alex Chebotarsky
    Alex Chebotarsky over 3 years
    @rockerest Please consider metioning non-array-generating solution in yours, since it's currently accepted one :) See answers by Salman A or Sebastian Umiński