How to limit the generation of pseudo-random numbers to non-zero and non-repeating numbers?

Asked

Viewed 843 times

11

I would like the figures not to repeat themselves and be different from zero.

import random


Sort1 = int(60 * random.random())   


Sort2 = int(60 * random.random())


Sort3 = int(60 * random.random())

Sort4 = int(60 * random.random())

Sort5 = int(60 * random.random())

Sort6 = int(60 * random.random())

lista = [Sort1,Sort2,Sort3,Sort4,Sort5,Sort6]

lista.sort()


print (Sort1,Sort2,Sort3,Sort4,Sort5,Sort6)


print (lista)

4 answers

12


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:

  1. 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
    
  2. 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)
    
  3. 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
        ...
    
  4. 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.

  • 1

    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!

11

If you want non-repeated numbers in a range ([1, 60], for your example), a very simple solution is to make a random permutation. Using numpy is pretty easy with the function numpy.random.permutation:

import numpy as np
nums = np.random.permutation(np.arange(1, 61))
print(nums)

That’s how this code works:

  1. The call np.arange(1, 61) generates a list with numbers from 1 to 60 (because you said the numbers should not be 0 - if you can have 0, just pass directly the maximum number+1 to the call from np.random.permutation next).
  2. This list serves as input to the call np.random.permutation, that just picks up that list and shuffles the positions.

Example of code execution output:

[ 3 37 59 54 58 16  1 19 36 40 34 31 18 13 25 50 23  9 41 46 27  8 15 47 24
 29 57 43 56 22 11 48 26 39  7 17 55 21 20 42 30 35 32 14 12 28 33 53 38 60
  2 52 44 45  6 49 10  5 51  4]
  • 1

    Good answer, but it is not necessary to reinvent the wheel - there is a function ready for it, in the same standard Python library (i.e. no numpy required). More details on my answer.

  • Ball show, @mgibsonbr. I didn’t know this function! :)

4

Here’s another alternative, using a set, this by default implemented in the python language itself already avoids duplicates.

In the example below n is the number of entries you want on your new list, put 6 based on the code you put in the question:

from random import randint

lista = set()
n = 6

while len(lista) < n:
    lista.add(randint(1, 60))

lista = sorted(lista) # transformar em lista e ordenar
print(lista) 

DEMONSTRATION

A lot of attention, if you happen to put a maximum number less than n, ex: 5, you will be in infinite loop, to cover that chance you can do:

from random import randint

lista = set()
n = 6
max_random = 60

if(max_random > n):
    while len(lista) < n:
        lista.add(randint(1, max_random))
    print(sorted(lista)) # transformar em lista e ordenar
else:
    print('O numero random não pode ser menor que n')

2

See if this solves your problem:

from random import randint

lista = []

n = 40

for i in range(n):
    num = randint(1, 60) # Intervalo de 1 a 60.
    while num == 0 or num in lista:
        num = randint(1, 60)
    lista.append(num)

lista.sort()

print lista
  • 1

    Beware when placing the "n" too large, it can run for a long time or even infinitely.

  • 3

    +1 for the good answer, but you’re absolutely right about it possibly running "infinitely". Therefore the ideal in these cases is to do a random permutation even (because this guarantees the finite execution time and no unnecessary loops are made). :)

Browser other questions tagged

You are not signed in. Login or sign up in order to post.