37

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?

2
  • 1
    This feels like an XY problem to me. What are you trying to accomplish by doing this? Commented Mar 24, 2017 at 12:08
  • 1
    I guess you could do the inverse and generate a list of numbers you do want to be allowed to return and then use random.choice() with that Commented Mar 24, 2017 at 12:11

8 Answers 8

53

Try this:

from random import choice print(choice([i for i in range(0,9) if i not in [2,5,7]])) 
Sign up to request clarification or add additional context in comments.

4 Comments

For python3 users its print(choice([i for i in range(0,9) if i not in [2,5,7]]))
This is actually quite slow for large range. The second ans is much faster.
Is it possible to generate non-zero uniform distribution numbers?
@jeevu94 Thx, fixed. I still miss print as a keyword though :)
7

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)) 

2 Comments

In the return of the function, should my_custom_random() not be random_exclude()?
Maybe something like this (I could be missing something): ``` from random import randint def random_exclude(exclude: set): randInt = randint(0,9) print(randInt) return random_exclude(exclude) if randInt in exclude else randInt exclude = [2, 5, 7,9,8,6,3,4] print(random_exclude(set(exclude))) ```
6

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

Comments

2

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.

Comments

1

This works nicely:

from random import choice exclude_this = [2, 5, 7] my_random_int = choice(list(set(range(0, 10)) - set(exclude_this))) 

Comments

1

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 

Comments

1

I would suggest:

from random import randint exclude = [2, 5, 7] while (number := randint(0, 9)) in exclude: pass 

The while loop will continue to assign randint(0, 9) to number end exit only if it is not in the exclude list.

The pass statement does nothing. It must be used when a statement is required syntactically but the program requires no action.

An explicit syntax could be:

from random import randint exclude = [2, 5, 7] while True: number = randint(0, 9) if number not in exclude: break 

At the end we can conclude that the code is not O(1), but optimistically efficient in case of a wider range.

2 Comments

Given that assignment expressions are new as of Python 3.8, could you explain this a little?
Sure @drmuelr, I updated the answer with some more explanation.
0

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.

1 Comment

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.