18

I would like get a random number in a range excluding one number (e.g. from 1 to 1000 exclude 577). I searched for a solution, but never solved my issue.

I want something like:

Math.floor((Math.random() * 1000) + 1).exclude(577);

I would like to avoid for loops creating an array as much as possible, because the length is always different (sometimes 1 to 10000, sometimes 685 to 888555444, etc), and the process of generating it could take too much time.

I already tried:

How could I achieve this?

Community
  • 1
  • 1
P. Frank
  • 5,691
  • 6
  • 22
  • 50
  • 3
    I don't understand the problem you're having and why the solutions you posted don't work for you. Wouldn't a simple while loop that checks for the forbidden number be sufficient? – j08691 Dec 09 '15 at 15:34
  • 8
    Get a random number from 1 to 99999, if the number is >= 577, then add 1. – ebyrob Dec 09 '15 at 15:34
  • 2
    @ebyrob ok but if is 99999 the number and i add 1 it make an error because 10000 not exsist. I dont want create "if" condition – P. Frank Dec 09 '15 at 15:36
  • @j08691: yes i would like is it possible without loop? – P. Frank Dec 09 '15 at 15:37
  • 1
    But logically, a loop makes the most sense. Get a random number, does this number equal the forbidden number? If so, then get a new random number. Unless you're randomly picking from something like an array that doesn't contain the forbidden number to start with, then you're forced to check on every pick. – j08691 Dec 09 '15 at 15:41
  • 1
    So generate the number, if it is in that list, generate a new one, continue until you have one. I highly doubt it will keep generating 557.... – epascarello Dec 09 '15 at 15:41
  • @P.Frank So go to 99998. (multiply by 99999 instead of 100000) – ebyrob Dec 09 '15 at 15:42
  • Thank you all for your help. – P. Frank Dec 09 '15 at 16:35
  • This QA **makes me physically sick**. I am **disgusted** that I live in the same universe as this QA. The only answer is Marco's http://stackoverflow.com/a/34184614/294884 – Fattie Jul 16 '16 at 15:30

8 Answers8

30

The fastest way to obtain a random integer number in a certain range [a, b], excluding one value c, is to generate it between a and b-1, and then increment it by one if it's higher than or equal to c.

Here's a working function:

function randomExcluded(min, max, excluded) {
    var n = Math.floor(Math.random() * (max-min) + min);
    if (n >= excluded) n++;
    return n;
}

This solution only has a complexity of O(1).

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
  • yes is fine but n++ get not random number but random + 1 – P. Frank Dec 09 '15 at 17:03
  • 3
    @P.Frank That's the trick! For example assume you want it from 1 to 10, but not 5. You generate it from 1 to 9, then, if it is higher than or equal to 5, you add 1. Doing this you can only obtain [1, 2, 3, 4, 6, 7, 8, 9, 10], which is what you want. All the numbers have the same probability to be generated, and 5 will never be generated. – Marco Bonelli Dec 09 '15 at 17:04
  • I have choose your solution, but the solution of @guest271314 is too good for me. thank you – P. Frank Dec 09 '15 at 17:11
  • 1
    What if i wanted to pass an array of numbers(any size)..say 5,6 & 7 to be excluded? How would I approach this? – Bmbariah Apr 14 '18 at 16:27
  • Why is the greater than important? Why can't we increment the random number only if n=excluded? @MarcoBonelli – Chirag Apr 12 '21 at 05:32
  • @Chirag if you do that you will never get the maximum value. – Marco Bonelli Apr 12 '21 at 10:58
5

One possibility is not to add 1, and if that number comes out, you assign the last possible value.

For example:

var result = Math.floor((Math.random() * 100000));
if(result==577) result = 100000;

In this way, you will not need to re-launch the random method, but is repeated. And meets the objective of being a random.

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
Jesus Cuesta
  • 175
  • 4
  • This also seems to work. Not sure assignment is any faster than addition but nice twist. Why the down vote? I think for multiple exclusions this would actually be faster. (and not require sorting) – ebyrob Dec 09 '15 at 15:46
  • this is the only correct algorithm, but Marco's version below is more elegant. – Fattie Feb 10 '16 at 12:28
3

As @ebyrob suggested, you can create a function that makes a mapping from a smaller set to the larger set with excluded values by adding 1 for each value that it is larger than or equal to:

// min - integer
// max - integer
// exclusions - array of integers
//            - must contain unique integers between min & max
function RandomNumber(min, max, exclusions) {
    // As @Fabian pointed out, sorting is necessary 
    // We use concat to avoid mutating the original array
    // See: http://stackoverflow.com/questions/9592740/how-can-you-sort-an-array-without-mutating-the-original-array
    var exclusionsSorted = exclusions.concat().sort(function(a, b) {
        return a - b
    });

    var logicalMax = max - exclusionsSorted.length;
    var randomNumber = Math.floor(Math.random() * (logicalMax - min + 1)) + min;

    for(var i = 0; i < exclusionsSorted.length; i++) {
        if (randomNumber >= exclusionsSorted[i]) {
            randomNumber++;
        }
    }

    return randomNumber;
}

Example Fiddle

Also, I think @JesusCuesta's answer provides a simpler mapping and is better.

Update: My original answer had many issues with it.

Stryner
  • 7,288
  • 3
  • 18
  • 18
1

You could just continue generating the number until you find it suits your needs:

function randomExcluded(start, end, excluded) {
    var n = excluded
    while (n == excluded)
        n = Math.floor((Math.random() * (end-start+1) + start));
    return n;
}

myRandom = randomExcluded(1, 10000, 577);

By the way this is not the best solution at all, look at my other answer for a better one!

Community
  • 1
  • 1
Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
  • 1
    This has infinite slowdown potential. – ebyrob Dec 09 '15 at 15:41
  • Yeah, if you think about it mathematically, but since that JS can generate thousands of randoms in a microsecond, this is never going to be slow. – Marco Bonelli Dec 09 '15 at 15:42
  • 1
    **very surprisingly you would suggest this Marco, given you know the correct answer below. should be deleted.** – Fattie Feb 10 '16 at 12:28
  • @JoeBlow this is still an answer, and there's no need to delete it because it answers the question properly, like many others do – Marco Bonelli Feb 10 '16 at 14:10
1

To expand on @Jesus Cuesta's answer:

function RandomNumber(min, max, exclusions) {
    var hash = new Object();
    for(var i = 0; i < exclusions.length; ++i ) {  // TODO: run only once as setup
       hash[exclusions[i]] = i + max - exclusions.length;
    }
    var randomNumber = Math.floor((Math.random() * (max - min - exclusions.length)) + min);
    if (hash.hasOwnProperty(randomNumber)) {
       randomNumber = hash[randomNumber];
    }
    return randomNumber;
}

Note: This only works if max - exclusions.length > maximum exclusion. So close.

ebyrob
  • 667
  • 7
  • 24
0

Generate a random number and if it matches the excluded number then add another random number(-20 to 20)

var max = 99999, min = 1, exclude = 577;
var num = Math.floor(Math.random() * (max - min)) + min ;
while(num == exclude || num > max || num < min ) {
    var rand = Math.random() > .5 ? -20 : 20 ;
    num += Math.floor((Math.random() * (rand));
}   
Gautham
  • 305
  • 4
  • 12
0
import random

def rng_generator():
  a = random.randint(0, 100)
  if a == 577:
    rng_generator()
  else:
    print(a)

#main()
rng_generator()
-2

Exclude the number from calculations:

function toggleRand() {
  // demonstration code only.
  // this algorithm does NOT produce random numbers.
  // return `0` - `576` , `578` - `n`  
  return [Math.floor((Math.random() * 576) + 1)
          ,Math.floor(Math.random() * (100000 - 578) + 1)
         ]
         // select "random" index 
         [Math.random() > .5 ? 0 : 1];
}

console.log(toggleRand());

Alternatively, use String.prototype.replace() with RegExp /^(577)$/ to match number that should be excluded from result; replace with another random number in range [0-99] utilizing new Date().getTime(), isNaN() and String.prototype.slice()

console.log(
  +String(Math.floor(Math.random()*(578 - 575) + 575))
  .replace(/^(577)$/,String(isNaN("$1")&&new Date().getTime()).slice(-2))
);

Could also use String.prototype.match() to filter results:

console.log(
  +String(Math.floor(Math.random()*10)) 
  .replace(/^(5)$/,String(isNaN("$1")&&new Date().getTime()).match(/[^5]/g).slice(-1)[0])
);
guest271314
  • 1
  • 15
  • 104
  • 177
  • Certainly an interesting solution, might hurt the random distribution though. – ebyrob Dec 09 '15 at 16:07
  • this is not the intended random distribution. The first time it's called, it only returns a number < 576. – Fabian Schmitthenner Dec 09 '15 at 16:18
  • @Fabian Could use `Math.random() > .5 ? 0 : 1` to select random index between `0` , `1` to return – guest271314 Dec 09 '15 at 16:20
  • yes, but e. g. 100 would have a higher probabolity than 900. I don't think that's what we want – Fabian Schmitthenner Dec 09 '15 at 16:22
  • @Fabian _"100 would have a higher probabolity than 900"_ ? What are 100 and 900 referencing ? Probability of what ? – guest271314 Dec 09 '15 at 16:24
  • Thank you man is the best way for me (without loop). Great job! – P. Frank Dec 09 '15 at 16:36
  • 2
    **This solution is very bad**! Splitting the range in two, and then looking on the first or the second part based on `Math.random() > .5 ? 0 : 1` will **NOT** generate pure random numbers, as the probability of obtaining each number is not equal. Take for example: `[Math.floor((Math.random() * 1) + 1), Math.floor(Math.random() * (100000 - 3 + 1))][Math.random() > .5 ? 0 : 1]`: it will generate `1` in 50% of the cases. – Marco Bonelli Dec 09 '15 at 16:38
  • @MarcoBonelli _"will NOT generate pure random numbers"_ Does `Math.random()` generate "pure random numbers" ? – guest271314 Dec 09 '15 at 16:40
  • @guest271314 as I said, look for example at `[Math.floor((Math.random() * 1) + 1), Math.floor(Math.random() * (100000 - 3 + 1))][Math.random() > .5 ? 0 : 1]`. The result would be 1 50% of the times, even though you are chosing a random number in the range [1, 100000]. – Marco Bonelli Dec 09 '15 at 16:41
  • @MarcoBonelli _"it will generate 1 in 50% of the cases"_ Yes, this is expected; though not in a linear sequence. Does `Math.random()` generate "pure random numbers" ? – guest271314 Dec 09 '15 at 16:43
  • @guest271314 I don't really think that getting 1 as result 50% of the times is the expected behavior if you want "a random number between 1 and 100000". – Marco Bonelli Dec 09 '15 at 16:44
  • @MarcoBonelli Perhaps there should be a clear definition of "random" ? From what able to grasp, here, `Math.random()` does not generate "pure random numbers" – guest271314 Dec 09 '15 at 16:45
  • @MarcoBonelli Also, if call `Math.random()` 100 times the result will not be 50% 1 : 50% 0 – guest271314 Dec 09 '15 at 16:51
  • @guest271314 of course, 100 are too few, it would take infinite tests to round up the value to 50%... – Marco Bonelli Dec 09 '15 at 16:53
  • is the best way for me, but have you a solution for have not 50% 1? – P. Frank Dec 09 '15 at 16:54
  • @guest271314 I think it's very hard to misconstrue the interpretation of random in this question as anything other than an equal opportunity of each number being picked (or as close as possible). – Stryner Dec 09 '15 at 16:54
  • @MarcoBonelli _"of course, 100 are too few, it would take **infinite** tests to round up the value to 50%..."_ Yes, this is correct. If required infinity to determine result, result cannot be determined accurately. _"it will generate 1 in 50% of the cases"_ cannot be proven – guest271314 Dec 09 '15 at 16:55
  • @P.Frank _"is the best way for me, but have you a solution for have not 50% 1?"_ "50% 1" is not correct, cannot be proven. – guest271314 Dec 09 '15 at 16:56
  • @P.Frank Try at `console` 1 or 100 times `var res = []; for (var i = 0; i< 100; i++) { res.push(Math.random() > .5 ? 0 : 1) }; var n = res.filter(function(n) { return n === 1 }); console.log(n.length)` . Observe how many times `n` `.length` is exactly `50` – guest271314 Dec 09 '15 at 16:57
  • @P.Frank I added another answer, check it [**here**](http://stackoverflow.com/questions/34182699/random-number-exclude-one/34184614#answer-34184614). – Marco Bonelli Dec 09 '15 at 17:01
  • Waoow very nice! The snippet does'nt work because the result is frequently 575 or 576 (more than 50%). Your first script `+String(Math.floor(Math.random()*1000)) .replace(/^(577)$/,String(isNaN("$1")&&new Date().getTime()).slice(-2))` is so brilliant! Now if i have 10 number and i used your script `+String(Math.floor(Math.random()*10)) .replace(/^(5)$/,String(isNaN("$1")&&new Date().getTime()).slice(-2))` i have result bigger 10. Please test `for(t=1;t<10;t++){ console.log(+String(Math.floor(Math.random()*10)) .replace(/^(5)$/,String(isNaN("$1")&&new Date().getTime()).slice(-2))) }` why? – P. Frank Dec 11 '15 at 08:07
  • @P.Frank `.slice(-2)` was used to meet requirement at Question , [0-576,578-1000] . Could substitue another string filter, for example, using `.match()` .See updated post `+String(Math.floor(Math.random()*10)) .replace(/^(5)$/,String(isNaN("$1")&&new Date().getTime()).match(/[^5]/))` – guest271314 Dec 11 '15 at 08:49
  • @JoeBlow _"this is incredibly wrong"_ What is "this" ? What is "incredibly wrong" ? – guest271314 Feb 10 '16 at 16:24
  • 2
    It simply ***does not*** generate random numbers. It's really a shame when non-mathematicians just make totally wild guesses at things like this: the problem is, this utter nonsense will now be seen on the internet for 1000 years. The poster here should just *click delete* for goodness sake. Note that this is a RIDICULOUSLY well-known problem in comp sci and the solution is absolutely trivial. Here's a full, long-winded explanation with source code http://stackoverflow.com/a/35315960/294884 Also the correct answer is given below at http://stackoverflow.com/a/34184614/294884 – Fattie Feb 10 '16 at 16:40
  • It's just so embarrassing -- the poster here's other comments (on "pure" random numbers etc.) unfortunately indicate simply no knowledge of the field in question. Anyway. That's the internet. – Fattie Feb 10 '16 at 16:42
  • @JoeBlow _"It simply does not generate random numbers."_ Which version ? – guest271314 Feb 10 '16 at 16:48
  • The fact that this answer is ticked makes me **physically ill**. This QA is **disgusting**. – Fattie Jul 16 '16 at 15:31