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?
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))
Try this:
from random import choice
print(choice([i for i in range(0,9) if i not in [2,5,7]]))
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
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.
This works nicely:
from random import choice
exclude_this = [2, 5, 7]
my_random_int = choice(list(set(range(0, 10)) - set(exclude_this)))
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
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.
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?
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))
Try this:
from random import choice
print(choice([i for i in range(0,9) if i not in [2,5,7]]))
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
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.
This works nicely:
from random import choice
exclude_this = [2, 5, 7]
my_random_int = choice(list(set(range(0, 10)) - set(exclude_this)))
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
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.