Generate random number in range excluding some numbers

Question:

Is there a simple way in Python to generate a random number in a range excluding some subset of numbers in that range?

For example, I know that you can generate a random number between 0 and 9 with:

from random import randint
randint(0,9)

What if I have a list, e.g. exclude=[2,5,7], that I don’t want to be returned?

Asked By: Kewl

||

Answers:

Try with something like this:

from random import randint

def random_exclude(*exclude):
  exclude = set(exclude)
  randInt = randint(0,9)
  return my_custom_random() if randInt in exclude else randInt 
  
print(random_exclude(2, 5, 7))
Answered By: rakwaht

Try this:

from random import choice

print(choice([i for i in range(0,9) if i not in [2,5,7]]))
Answered By: McGrady

If you have larger lists, i would recommend to use set operations because they are noticeable faster than the recomended answer.

random.choice(list(set([x for x in range(0, 9)]) - set(to_exclude)))

I took took a few tests with both the accepted answer and my code above.

For each test i did 50 iterations and measured the average time.
For testing i used a range of 999999.

to_exclude size 10 elements:
Accepted answer = 0.1782s
This answer = 0.0953s

to_exclude size 100 elements:
Accepted answer = 01.2353s
This answer = 00.1117s

to_exclude size 1000 elements:
Accepted answer = 10.4576s
This answer = 00.1009s

Answered By: Dominic Nagel

Here’s another way of doing it that doesn’t use random.choice or repeat itself until it gets it right:

import random

def random_exclusion(start, stop, excluded) -> int:
    """Function for getting a random number with some numbers excluded"""
    excluded = set(excluded)
    value = random.randint(start, stop - len(excluded)) # Or you could use randrange
    for exclusion in tuple(excluded):
        if value < exclusion:
            break
        value += 1
    return value

What this does is it gets a number between the start and the stop minus the amount of excluded numbers. Then it adds 1 to the number until if it is above any of the exclusions.

Let’s use an example of a random number between 0 and 5, excluding 3. Since we subtracted the 5 by 1, we can have 0, 1, 2, 3, or 4. But we want to shift the last 2 forward by 1 to prevent a 3, giving us 0, 1, 2, 4, or 5. This doesn’t create a list with excluded values then pick a random from it, it gets a value and adds to it, which is a significant time and memory save.

I performed Dominic Nagel’s tests (with time.perf_counter()) to see which was faster:

for 10 elements:
This way’s time: 0.00000162599899340421
His time: 0.19212667199899441384

for 100 elements:
0.00000543000060133636
0.18264625200070441768

for 1000 elements:
0.00004090999893378467
0.21630024799902458632

for 10000 elements:
0.00087945000152103605
0.19593418199801818091

This one goes up exponentially, while his stays relatively the same at around 0.2 seconds, so if you’re going to be taking away a billion elements, I would most likely stick with his unless you are using a compiled programming language. Still, this method saves a lot of memory, so if you’re going for that, then you should probably stick with mine.

Answered By: TeaCoast

This works nicely:

from random import choice

exclude_this = [2, 5, 7]
my_random_int = choice(list(set(range(0, 10)) - set(exclude_this)))
Answered By: Rune Kaagaard

Using sets (and choice()) is a faster solution than comprehending a list. Such as:

from numpy.random import randint
from random import choice
from typing import Set

def my_rand(start: int, end: int, exclude_values: Set[int] = None):
    if not exclude_values: # in this case, there are no values to exclude so there is no point in filtering out any
        return randint(start, end)
    return choice(list(set(range(start, end)).difference(exclude_values)))

As shown via timeit (the statements in these tests are messy and include redundant statements, like if True, but this is to emulate checks that the function above would do):

  • Sets
>>> timeit.timeit(setup="from random import choice", stmt="choice(list(set(range(0, 300)).difference(set({1, 2, 3, 80, 189, 273}) if True else set())))", number=10000)
0.15672149998135865
>>> timeit.timeit(setup="from random import choice", stmt="choice(list(set(range(0, 300)).difference(set({1, 2, 3, 80, 189, 273}) if True else set())))", number=10000)
0.1651422999566421
>>> timeit.timeit(setup="from random import choice", stmt="choice(list(set(range(0, 300)).difference(set({1, 2, 3, 80, 189, 273}) if True else set())))", number=10000)
0.16615699999965727
  • Comprehension:
>>> timeit.timeit(setup="from random import choice", stmt="choice([i for i in range(0, 300) if True and i not in set({1, 2, 3, 80, 189, 273})])", number=10000)
0.8613313999958336
>>> timeit.timeit(setup="from random import choice", stmt="choice([i for i in range(0, 300) if True and i not in set({1, 2, 3, 80, 189, 273})])", number=10000)
0.9154910000506788
>>> timeit.timeit(setup="from random import choice", stmt="choice([i for i in range(0, 300) if True and i not in set({1, 2, 3, 80, 189, 273})])", number=10000)
0.9114390999311581
Answered By: Taylor Courtney

This is an old post, but maybe someone else needs an idea.
To avoid wasting time looping for useful random number, I suggest you create a list from 0 to 9 using for loop [0,1,....9,]. then you shuffle this list once randomly.
[ 4,8,0,....1]
to get a random number, just "poll" the first number from this list each time you want a random number (which will not exist in the list the next time read).

You can remove the number from the list or use an index to parse the shuffled list.

Answered By: Salim Zerrougui
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.