One of the most common tasks that requires random action is selecting one item from a group, be it a character from a string, unicode, or buffer, a byte from a bytearray, or an item from a list, tuple, set, or xrange. It's also common to want a sample of more than one item.
Don't do this when randomly selecting an item
A naive approach to these tasks involves something like the following; to select a single item, you would use randrange
(or randint
) from the random
module, which generates a pseudo-random integer from the range indicated by its arguments:
[python]
import random
items = ['here', 'are', 'some', 'strings', 'of',
'which', 'we', 'will', 'select', 'one']
rand_item = items[random.randrange(len(items))]
[/python]
An equally naive way to select multiple items might use random.randrange
to generate indexes inside a list comprehension, as in:
[python]
rand_items = [items[random.randrange(len(items))]
for item in range(4)]
[/python]
These work, but as you should expect if you've been writing Python for any length of time, there's a built-in way of doing it that is briefer and more readable.
Do this instead, when selecting an item
The pythonic way to select a single item from a Python sequence type — that's any of str
, unicode
, list
, tuple
, bytearray
, buffer
, xrange
— is to use random.choice
. For example, the last line of our single-item selection would be:
[python]
rand_item = random.choice(items)
[/python]
Much simpler, isn't it? There's an equally simple way to select n items from the sequence:
[python]
rand_items = random.sample(items, n)
[/python]
Randomly selecting from a set
sets
are not indexable, meaning set([1, 2, 3])[0]
produces an error. Therefore random.choice
does not support sets
, however random.sample
does.
For example:
[python]
>>> from random import choice, sample
>>>
>>> # INVALID: set([1, 2, 3])[0]
>>> choice(set([1, 2, 3, 4, 5]))
Traceback (most recent call last):
File "
File "
return seq[int(self.random() * len(seq))] # raises IndexError if seq is empty
TypeError: 'set' object does not support indexing
[/python]
There are several ways to get around this, 2 of which are to convert the set
to a list
first, and to use random.sample
which does support sets
.
Example:
[python]
>>> from random import choice, sample
>>>
>>> # Convert the set to a list
>>> choice(list(set([1, 2, 3])))
1
>>>
>>> # random.sample(), selecting 1 random element
>>> sample(set([1, 2, 3]), 1)
[1]
>>> sample(set([1, 2, 3]), 1)[0]
3
[/python]
Duplicate Items
If the sequence contains duplicate values, each one is an independent candidate for selection. To avoid duplicates, one method would be to convert the list
into a set
, and back into a list
. For example:
[python]
>>> my_list = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5]
>>> my_set = set(my_list)
>>> my_list = list(my_set) # No duplicates
>>> my_list
[1, 2, 3, 4, 5]
>>> my_elem = random.choice(my_list)
>>> my_elem
2
>>> another_elem = random.choice(list(set([1, 1, 1])))
[/python]