Use random.sample
:
>>> random.sample(range(1,61), 6)
[39, 15, 37, 18, 52, 60]
Explanation:
A method that guarantees a fair draw (i.e. each element has the same chance of being drawn) and without repetition is the generation of a permutation of the search space - for example by fisher-yates algorithm - from which only the first k
elements that one wants.
If your search space is small, the implementation proposed by Luiz Vieira (create a list of all the elements and exchange it) is the simplest and perhaps most efficient way. In your question, the numbers only go from 1
to 60
, then this simple solution is the one I would use. If you don’t have access to numpy, you can also use the function random.shuffle
:
>>> nums = list(range(1, 61))
>>> random.shuffle(nums)
>>> nums[:6]
[14, 12, 56, 26, 42, 10]
On the other hand, if the search space were too large (e.g., you want 10 numbers from 1 to 1 billion) this implementation would be unviable - not so much for the time but for the memory spent creating the list. In that case, an alternative implementation would be:
def random_elements(a_sortear, total):
state = {}
for i in range(a_sortear):
# Troca o state[i] com um elemento aleatório
swap_with = random.randint(i, total - 1)
state[i], state[swap_with] = state.get(swap_with, swap_with), state.get(i, i)
return [state[i]+1 for i in range(a_sortear)]
print (random_elements(10, 1000000000))
Source (adapted)
This is a partial application of the same algorithm:
Instead of the list being explicitly created, it is implied that the index element i
holds the value i
if it is absent from the set:
state.get(swap_with, swap_with) # O valor padrão, se ausente, é o próprio índice
When two elements are exchanged (such as the original algorithm), they are explicitly placed in the set:
state[i], state[swap_with] = state.get(swap_with, swap_with), state.get(i, i)
The algorithm stops permuting when the desired number of elements has already been obtained:
for i in range(a_sortear): # Não vai até total, mas para em a_sortear
swap_with = random.randint(i, total - 1) # Sorteia de 0 a total-1
...
For the result to go from 1
to N
, instead of 0
to N-1
, moves the whole set 1 to the right:
return [state[i]+1 for i in range(a_sortear)]
That is, in this implementation both the time and the memory spent are proportional to the number of elements that one wants, instead of the total of elements available to be drawn.
Finally, there is the alternative algorithm where you simply draw several numbers and, if any are repeated, the draw is done again until all the results are different. This method can be even more efficient than the partial application of Fisher-Yates, when the total set is very large (since the chance of collision is small). The answers icarus Dantas and miguel’s give examples of implementation.
The use of random.sample
- from what I understand of sources - choose one or another implementation based on what would be most efficient depending on the sizes of the total set and the number of elements to be drawn.
I had already raised Luiz Vieira’s, but I was expecting an alternative with "moderate" F-Y (I hope the staff notice the subtlety of shuffling only the number of items required). Therefore, +1 also!
– Bacco