Como faço para criar uma lista de números aleatórios sem duplicatas?


110

Tentei usar random.randint(0, 100), mas alguns números eram iguais. Existe um método / módulo para criar uma lista de números aleatórios exclusivos?

Observação: o código a seguir é baseado em uma resposta e foi adicionado depois que a resposta foi postada. Não faz parte da questão; é a solução.

def getScores():
    # open files to read and write
    f1 = open("page.txt", "r");
    p1 = open("pgRes.txt", "a");

    gScores = [];
    bScores = [];
    yScores = [];

    # run 50 tests of 40 random queries to implement "bootstrapping" method 
    for i in range(50):
        # get 40 random queries from the 50
        lines = random.sample(f1.readlines(), 40);

1
Se forem únicos, podem ser verdadeiramente aleatórios no contexto certo. Como uma amostra aleatória de índices sem reposição, ainda pode ser completamente aleatória.
gbtimmon

Respostas:


180

Isso retornará uma lista de 10 números selecionados no intervalo de 0 a 99, sem duplicatas.

import random
random.sample(range(100), 10)

Com referência ao seu exemplo de código específico, você provavelmente deseja ler todas as linhas do arquivo uma vez e, em seguida, selecionar linhas aleatórias da lista salva na memória. Por exemplo:

all_lines = f1.readlines()
for i in range(50):
    lines = random.sample(all_lines, 40)

Dessa forma, você só precisa realmente ler o arquivo uma vez, antes do loop. É muito mais eficiente fazer isso do que voltar ao início do arquivo e chamar f1.readlines()novamente para cada iteração de loop.


2
Essa técnica desperdiça memória, especialmente para grandes amostras. Eu postei um código para uma solução muito mais eficiente de memória e computação abaixo que usa um Gerador Congruencial Linear.
Thomas Lux

Foi apontado para mim que o método LCG é menos "aleatório", portanto, se você quiser gerar muitas sequências aleatórias exclusivas, a variedade será menor do que esta solução. Se você só precisa de um punhado de sequências aleatórias, LCG é o caminho a seguir!
Thomas Lux

Obrigado Greg, Foi útil
N Sivaram

15

Você pode usar a função shuffle do módulo aleatório como este:

import random

my_list = list(xrange(1,100)) # list of integers from 1 to 99
                              # adjust this boundaries to fit your needs
random.shuffle(my_list)
print my_list # <- List of unique random numbers

Observe aqui que o método shuffle não retorna nenhuma lista como esperado, ele apenas embaralha a lista passada por referência.


É bom mencionar aqui que xrange funciona apenas em Python 2 e não em Python 3.
Shayan Shafiq

10

Você pode primeiro criar uma lista de números de aa b, onde ae bsão, respectivamente, o menor e o maior número em sua lista e, em seguida, embaralhá-la com o algoritmo de Fisher-Yates ou usando o random.shufflemétodo do Python .


1
Gerar uma lista completa de índices é um desperdício de memória, especialmente para grandes amostras. Eu postei um código para uma solução muito mais eficiente de memória e computação abaixo que usa um Gerador Congruencial Linear.
Thomas Lux

8

A solução apresentada nesta resposta funciona, mas pode se tornar problemática com a memória se o tamanho da amostra for pequeno, mas a população for enorme (por exemplo random.sample(insanelyLargeNumber, 10)).

Para consertar isso, eu iria com isso:

answer = set()
sampleSize = 10
answerSize = 0

while answerSize < sampleSize:
    r = random.randint(0,100)
    if r not in answer:
        answerSize += 1
        answer.add(r)

# answer now contains 10 unique, random integers from 0.. 100

Agora random.sampleusa essa abordagem para um pequeno número de amostras de uma grande população, portanto, esse problema com a memória realmente não existe mais. Embora, no momento em que esta resposta foi escrita, a implementação de random.shufflepode ter sido diferente.
kyrill

5

Gerador de número pseudoaleatório congruencial linear

O (1) Memória

Operações O (k)

Este problema pode ser resolvido com um Gerador Congruencial Linear simples . Isso requer sobrecarga de memória constante (8 inteiros) e no máximo 2 * (comprimento de sequência) cálculos.

Todas as outras soluções usam mais memória e mais computação! Se você só precisar de algumas sequências aleatórias, esse método será significativamente mais barato. Para intervalos de tamanho N, se você deseja gerar na ordem de sequências Núnicas kou mais, eu recomendo a solução aceita usando os métodos integrados, random.sample(range(N),k)pois isso foi otimizado em python para velocidade.

Código

# Return a randomized "range" using a Linear Congruential Generator
# to produce the number sequence. Parameters are the same as for 
# python builtin "range".
#   Memory  -- storage for 8 integers, regardless of parameters.
#   Compute -- at most 2*"maximum" steps required to generate sequence.
#
def random_range(start, stop=None, step=None):
    import random, math
    # Set a default values the same way "range" does.
    if (stop == None): start, stop = 0, start
    if (step == None): step = 1
    # Use a mapping to convert a standard range into the desired range.
    mapping = lambda i: (i*step) + start
    # Compute the number of numbers in this range.
    maximum = (stop - start) // step
    # Seed range with a random integer.
    value = random.randint(0,maximum)
    # 
    # Construct an offset, multiplier, and modulus for a linear
    # congruential generator. These generators are cyclic and
    # non-repeating when they maintain the properties:
    # 
    #   1) "modulus" and "offset" are relatively prime.
    #   2) ["multiplier" - 1] is divisible by all prime factors of "modulus".
    #   3) ["multiplier" - 1] is divisible by 4 if "modulus" is divisible by 4.
    # 
    offset = random.randint(0,maximum) * 2 + 1      # Pick a random odd-valued offset.
    multiplier = 4*(maximum//4) + 1                 # Pick a multiplier 1 greater than a multiple of 4.
    modulus = int(2**math.ceil(math.log2(maximum))) # Pick a modulus just big enough to generate all numbers (power of 2).
    # Track how many random numbers have been returned.
    found = 0
    while found < maximum:
        # If this is a valid value, yield it in generator fashion.
        if value < maximum:
            found += 1
            yield mapping(value)
        # Calculate the next value in the sequence.
        value = (value*multiplier + offset) % modulus

Uso

O uso desta função "intervalo_aleatório" é o mesmo que para qualquer gerador (como "intervalo"). Um exemplo:

# Show off random range.
print()
for v in range(3,6):
    v = 2**v
    l = list(random_range(v))
    print("Need",v,"found",len(set(l)),"(min,max)",(min(l),max(l)))
    print("",l)
    print()

Resultados da amostra

Required 8 cycles to generate a sequence of 8 values.
Need 8 found 8 (min,max) (0, 7)
 [1, 0, 7, 6, 5, 4, 3, 2]

Required 16 cycles to generate a sequence of 9 values.
Need 9 found 9 (min,max) (0, 8)
 [3, 5, 8, 7, 2, 6, 0, 1, 4]

Required 16 cycles to generate a sequence of 16 values.
Need 16 found 16 (min,max) (0, 15)
 [5, 14, 11, 8, 3, 2, 13, 1, 0, 6, 9, 4, 7, 12, 10, 15]

Required 32 cycles to generate a sequence of 17 values.
Need 17 found 17 (min,max) (0, 16)
 [12, 6, 16, 15, 10, 3, 14, 5, 11, 13, 0, 1, 4, 8, 7, 2, ...]

Required 32 cycles to generate a sequence of 32 values.
Need 32 found 32 (min,max) (0, 31)
 [19, 15, 1, 6, 10, 7, 0, 28, 23, 24, 31, 17, 22, 20, 9, ...]

Required 64 cycles to generate a sequence of 33 values.
Need 33 found 33 (min,max) (0, 32)
 [11, 13, 0, 8, 2, 9, 27, 6, 29, 16, 15, 10, 3, 14, 5, 24, ...]

1
Isso é muito legal! Mas tenho certeza de que realmente responde à pergunta; digamos que eu queira amostrar 2 valores de 0 a 4. Sem gerar meus próprios prime, a função retornará apenas 4 respostas possíveis, porque valueé a única coisa escolhida aleatoriamente com 4 valores possíveis, quando precisamos de pelo menos (4 escolha 2) = 6, (permitindo a ordenação não aleatória). random_range(2,4)retornará os valores {(1, 0), (3, 2), (2, 1), (0, 3)}, mas nunca o par (3,1) (ou (1,3)). Você está esperando novos números primos grandes gerados aleatoriamente a cada chamada de função?
wowserx

1
(Também estou assumindo que você espera que as pessoas embaralhem a sequência depois que sua função a retornar se quiserem uma ordem aleatória, já que random_range(v)retorna até vsequências únicas em vez de v!)
wowserx

Completamente verdade! É difícil equilibrar entre evitar o estouro de inteiros e gerar sequências aleatórias suficientes. Atualizei a função para incorporar um pouco mais de aleatoriedade, mas ainda não é tão aleatória quanto v !. Depende se você deseja usar a função várias vezes. Esta solução é mais bem usada quando você está gerando a partir de uma grande variedade de valores (quando o consumo de memória de outras pessoas seria muito maior). Vou pensar mais nisso, obrigado!
Thomas Lux

4

Se a lista de N números de 1 a N for gerada aleatoriamente, então sim, existe a possibilidade de que alguns números possam se repetir.

Se você quiser uma lista de números de 1 a N em uma ordem aleatória, preencha um array com inteiros de 1 a N e, em seguida, use um shuffle de Fisher-Yates ou Python random.shuffle().


3

Se você precisa amostrar números extremamente grandes, você não pode usar range

random.sample(range(10000000000000000000000000000000), 10)

porque joga:

OverflowError: Python int too large to convert to C ssize_t

Além disso, se random.samplenão for possível produzir o número de itens que você deseja devido ao intervalo ser muito pequeno

 random.sample(range(2), 1000)

lança:

 ValueError: Sample larger than population

Esta função resolve os dois problemas:

import random

def random_sample(count, start, stop, step=1):
    def gen_random():
        while True:
            yield random.randrange(start, stop, step)

    def gen_n_unique(source, n):
        seen = set()
        seenadd = seen.add
        for i in (i for i in source() if i not in seen and not seenadd(i)):
            yield i
            if len(seen) == n:
                break

    return [i for i in gen_n_unique(gen_random,
                                    min(count, int(abs(stop - start) / abs(step))))]

Uso com números extremamente grandes:

print('\n'.join(map(str, random_sample(10, 2, 10000000000000000000000000000000))))

Resultado da amostra:

7822019936001013053229712669368
6289033704329783896566642145909
2473484300603494430244265004275
5842266362922067540967510912174
6775107889200427514968714189847
9674137095837778645652621150351
9969632214348349234653730196586
1397846105816635294077965449171
3911263633583030536971422042360
9864578596169364050929858013943

Uso em que o intervalo é menor que o número de itens solicitados:

print(', '.join(map(str, random_sample(100000, 0, 3))))

Resultado da amostra:

2, 0, 1

Também funciona com intervalos e etapas negativas:

print(', '.join(map(str, random_sample(10, 10, -10, -2))))
print(', '.join(map(str, random_sample(10, 5, -5, -2))))

Resultados da amostra:

2, -8, 6, -2, -4, 0, 4, 10, -6, 8
-3, 1, 5, -1, 3

e se você gerar mais de 8 bilhões de números, mais cedo ou mais tarde se tornará muito grande
david_adler

Essa resposta tem uma falha grave para grandes amostras. A probabilidade de colisão aumenta linearmente com cada etapa. Eu postei uma solução usando um Gerador Congruencial Linear que tem O (1) overhead de memória e O (k) etapas necessárias para gerar k números. Isso pode ser resolvido com muito mais eficiência!
Thomas Lux

Esta resposta é definitivamente melhor se você quiser gerar várias sequências aleatórias na ordem do comprimento da sequência! O método LCG é menos "aleatório" quando se trata de gerar várias sequências exclusivas.
Thomas Lux

"Esta função resolve os dois problemas" Como ela resolve o segundo problema? Você ainda não pode obter 1000 amostras de uma população de 2. Em vez de lançar uma exceção, você produz um resultado incorreto; isso dificilmente é uma resolução do "problema" (o que realmente não é um problema, já que não é de todo razoável solicitar k amostras únicas de uma população de n <k ).
kyrill

1

Você pode usar a biblioteca Numpy para uma resposta rápida, conforme mostrado abaixo -

O trecho de código dado lista 6 números únicos entre o intervalo de 0 a 5. Você pode ajustar os parâmetros para seu conforto.

import numpy as np
import random
a = np.linspace( 0, 5, 6 )
random.shuffle(a)
print(a)

Resultado

[ 2.  1.  5.  3.  4.  0.]

Ele não impõe nenhuma restrição como vemos em random.sample, conforme referido aqui .

Espero que isso ajude um pouco.


1

A resposta fornecida aqui funciona muito bem com relação ao tempo e também à memória, mas um pouco mais complicada, pois usa construções Python avançadas, como rendimento. A resposta mais simples funciona bem na prática, mas o problema dessa resposta é que ela pode gerar muitos inteiros espúrios antes de realmente construir o conjunto necessário. Experimente com PopulaçãoSize = 1000, TamanhoSamostra = 999. Em teoria, existe uma chance de que ele não termine.

A resposta abaixo aborda ambas as questões, pois é determinística e um tanto eficiente, embora atualmente não seja tão eficiente quanto as outras duas.

def randomSample(populationSize, sampleSize):
  populationStr = str(populationSize)
  dTree, samples = {}, []
  for i in range(sampleSize):
    val, dTree = getElem(populationStr, dTree, '')
    samples.append(int(val))
  return samples, dTree

onde as funções getElem, percolateUp são definidas abaixo

import random

def getElem(populationStr, dTree, key):
  msd  = int(populationStr[0])
  if not key in dTree.keys():
    dTree[key] = range(msd + 1)
  idx = random.randint(0, len(dTree[key]) - 1)
  key = key +  str(dTree[key][idx])
  if len(populationStr) == 1:
    dTree[key[:-1]].pop(idx)
    return key, (percolateUp(dTree, key[:-1]))
  newPopulation = populationStr[1:]
  if int(key[-1]) != msd:
    newPopulation = str(10**(len(newPopulation)) - 1)
  return getElem(newPopulation, dTree, key)

def percolateUp(dTree, key):
  while (dTree[key] == []):
    dTree[key[:-1]].remove( int(key[-1]) )
    key = key[:-1]
  return dTree

Finalmente, o tempo em média foi de cerca de 15 ms para um grande valor de n como mostrado abaixo

In [3]: n = 10000000000000000000000000000000

In [4]: %time l,t = randomSample(n, 5)
Wall time: 15 ms

In [5]: l
Out[5]:
[10000000000000000000000000000000L,
 5731058186417515132221063394952L,
 85813091721736310254927217189L,
 6349042316505875821781301073204L,
 2356846126709988590164624736328L]

Você acha que essa resposta é complicada? O que é isso então ?! E há a outra resposta , que gera muitos "inteiros espúrios". Eu executei sua implementação com a entrada de exemplo que você forneceu (populaçãoSize = 1000, sampleSize = 999). Sua versão chama a random.randintfunção 3996 vezes, enquanto a outra cca. 6.000 vezes. Não é uma melhoria tão grande, hein?
kyrill

@kyrill, sua opinião sobre esta resposta
aak318

1

Para obter um programa que gere uma lista de valores aleatórios sem duplicatas que seja determinística, eficiente e construída com construções básicas de programação, considere a função extractSamplesdefinida abaixo,

def extractSamples(populationSize, sampleSize, intervalLst) :
    import random
    if (sampleSize > populationSize) :
        raise ValueError("sampleSize = "+str(sampleSize) +" > populationSize (= " + str(populationSize) + ")")
    samples = []
    while (len(samples) < sampleSize) :
        i = random.randint(0, (len(intervalLst)-1))
        (a,b) = intervalLst[i]
        sample = random.randint(a,b)
        if (a==b) :
            intervalLst.pop(i)
        elif (a == sample) : # shorten beginning of interval                                                                                                                                           
            intervalLst[i] = (sample+1, b)
        elif ( sample == b) : # shorten interval end                                                                                                                                                   
            intervalLst[i] = (a, sample - 1)
        else :
            intervalLst[i] = (a, sample - 1)
            intervalLst.append((sample+1, b))
        samples.append(sample)
    return samples

A ideia básica é acompanhar os intervalos intervalLstde valores possíveis a partir dos quais selecionar nossos elementos necessários. Isso é determinístico no sentido de que temos a garantia de gerar uma amostra dentro de um número fixo de etapas (apenas dependente de populationSizeesampleSize ).

Para usar a função acima para gerar nossa lista necessária,

In [3]: populationSize, sampleSize = 10**17, 10**5

In [4]: %time lst1 = extractSamples(populationSize, sampleSize, [(0, populationSize-1)])
CPU times: user 289 ms, sys: 9.96 ms, total: 299 ms
Wall time: 293 ms

Também podemos comparar com uma solução anterior (para um valor inferior de populaçãoSize)

In [5]: populationSize, sampleSize = 10**8, 10**5

In [6]: %time lst = random.sample(range(populationSize), sampleSize)
CPU times: user 1.89 s, sys: 299 ms, total: 2.19 s
Wall time: 2.18 s

In [7]: %time lst1 = extractSamples(populationSize, sampleSize, [(0, populationSize-1)])
CPU times: user 449 ms, sys: 8.92 ms, total: 458 ms
Wall time: 442 ms

Observe que reduzi o populationSizevalor, pois ele produz Erro de Memória para valores mais altos ao usar a random.samplesolução (também mencionado nas respostas anteriores aqui e aqui ). Para os valores acima, também podemos observar que extractSamplessupera a random.sampleabordagem.

PS: Embora a abordagem central seja semelhante à minha resposta anterior , há modificações substanciais na implementação, bem como na abordagem, juntamente com a melhoria na clareza.


0

Uma função muito simples que também resolve seu problema

from random import randint

data = []

def unique_rand(inicial, limit, total):

        data = []

        i = 0

        while i < total:
            number = randint(inicial, limit)
            if number not in data:
                data.append(number)
                i += 1

        return data


data = unique_rand(1, 60, 6)

print(data)


"""

prints something like 

[34, 45, 2, 36, 25, 32]

"""

0

O problema com as abordagens baseadas em conjunto ("se o valor aleatório em valores de retorno, tente novamente") é que seu tempo de execução é indeterminado devido a colisões (que requerem outra iteração "tente novamente"), especialmente quando uma grande quantidade de valores aleatórios é retornada da gama.

Uma alternativa que não está sujeita a esse tempo de execução não determinístico é a seguinte:

import bisect
import random

def fast_sample(low, high, num):
    """ Samples :param num: integer numbers in range of
        [:param low:, :param high:) without replacement
        by maintaining a list of ranges of values that
        are permitted.

        This list of ranges is used to map a random number
        of a contiguous a range (`r_n`) to a permissible
        number `r` (from `ranges`).
    """
    ranges = [high]
    high_ = high - 1
    while len(ranges) - 1 < num:
        # generate a random number from an ever decreasing
        # contiguous range (which we'll map to the true
        # random number).
        # consider an example with low=0, high=10,
        # part way through this loop with:
        #
        # ranges = [0, 2, 3, 7, 9, 10]
        #
        # r_n :-> r
        #   0 :-> 1
        #   1 :-> 4
        #   2 :-> 5
        #   3 :-> 6
        #   4 :-> 8
        r_n = random.randint(low, high_)
        range_index = bisect.bisect_left(ranges, r_n)
        r = r_n + range_index
        for i in xrange(range_index, len(ranges)):
            if ranges[i] <= r:
                # as many "gaps" we iterate over, as much
                # is the true random value (`r`) shifted.
                r = r_n + i + 1
            elif ranges[i] > r_n:
                break
        # mark `r` as another "gap" of the original
        # [low, high) range.
        ranges.insert(i, r)
        # Fewer values possible.
        high_ -= 1
    # `ranges` happens to contain the result.
    return ranges[:-1]

0
import random

sourcelist=[]
resultlist=[]

for x in range(100):
    sourcelist.append(x)

for y in sourcelist:
    resultlist.insert(random.randint(0,len(resultlist)),y)

print (resultlist)

1
Bem-vindo ao Stackoverflow. Explique sua resposta por que e como isso resolve o problema para que outras pessoas possam entender sua resposta facilmente.
outubro

Embora este código possa resolver a questão, incluir uma explicação de como e por que isso resolve o problema realmente ajudaria a melhorar a qualidade de sua postagem e provavelmente resultaria em mais votos positivos. Lembre-se de que você está respondendo às perguntas dos leitores no futuro, não apenas da pessoa que está perguntando agora. Por favor edite sua resposta para adicionar explicações e dar uma indicação do que limitações e premissas se aplicam. Da avaliação
double-beep

-1

Se você deseja garantir que os números adicionados são únicos, você pode usar um objeto Set

se estiver usando 2.7 ou superior, ou importe o módulo sets se não.

Como outros mencionaram, isso significa que os números não são verdadeiramente aleatórios.


-1

para amostrar inteiros sem substituição entre minvale maxval:

import numpy as np

minval, maxval, n_samples = -50, 50, 10
generator = np.random.default_rng(seed=0)
samples = generator.permutation(np.arange(minval, maxval))[:n_samples]

# or, if minval is 0,
samples = generator.permutation(maxval)[:n_samples]

com jax:

import jax

minval, maxval, n_samples = -50, 50, 10
key = jax.random.PRNGKey(seed=0)
samples = jax.random.shuffle(key, jax.numpy.arange(minval, maxval))[:n_samples]

Por que você geraria uma permutaçãode um número possivelmente grande de elementos e então selecionaria apenas o primeiro n_samplesdeles? Qual é o seu raciocínio por trás dessa abordagem? Você pode explicar quais são as vantagens de sua abordagem, em comparação com qualquer uma do grande número de respostas existentes (a maioria delas de 8 anos atrás)?
kyrill de

na verdade, minha resposta tem complexidade semelhante a outras respostas mais votadas e é mais rápida porque usa numpy. outros métodos mais votados usam random.shuffle, que usa Mersenne Twister, que é muito mais lento do que algos oferecido por numpy (e provavelmente jax). numpy e jax permitem outros algoritmos de geração de números aleatórios. jax também permite a compilação e diferenciação jit, o que pode ser útil para a diferenciação estocástica. Além disso, em relação a uma matriz "possivelmente grande", algumas das respostas mais votadas fazem exatamente a mesma coisa com random.shuffle, o que eu não acho que seja pecaminoso em um sentido relativo ou mesmo absoluto
grisaitis

1
Não tenho certeza do que você quer dizer com " random.shuffleusa Mersenne Twister" - é embaralhamento Fisher-Yates, conforme mencionado em várias respostas. Tem complexidade de tempo linear, portanto, não pode ser assintoticamente mais lento do que algoritmos oferecidos por qualquer outra biblioteca, numpy ou não. Se numpy é mais rápido, é só porque está implementado em C, mas isso não garante a geração de uma permutação enorme (que pode nem caber na memória), apenas para escolher alguns elementos dela. Não há uma única resposta além da sua que faça isso.
kyrill

Minhas desculpas, eu li que python random usou Mersenne Twister como prng. Você tem uma fonte para que eu possa aprender mais sobre Fisher Yates e o papel em random.shuffle?
grisaitis

Já existem dois links separados para a Wikipedia em duas respostas distintas aqui. Se a Wikipedia não for uma fonte boa o suficiente para você, há 14 referências no final do artigo. E há o Google. Isso ajuda? Ah, e o randommódulo é escrito em Python, então você pode facilmente visualizar seu código-fonte (tente random.__file__).
kyrill

-3

Da CLI no win xp:

python -c "import random; print(sorted(set([random.randint(6,49) for i in range(7)]))[:6])"

No Canadá, temos a Loteria 6/49. Acabei de embrulhar o código acima em lotto.bat e executar C:\home\lotto.batou apenas C:\home\lotto.

Porque random.randintmuitas vezes repete um número, eu uso setcomrange(7) e encurto para 6.

Ocasionalmente, se um número se repetir mais de 2 vezes, o comprimento da lista resultante será menor que 6.

EDIT: No entanto, random.sample(range(6,49),6)é o caminho correto a seguir.


-3
import random
result=[]
for i in range(1,50):
    rng=random.randint(1,20)
    result.append(rng)

1
Você poderia explicar como isso evita duplicatas? Não é óbvio neste despejo de código.
Toby Speight,

Não é verdade. print len(result), len(set(result)). Você esperaria ver que resultteria elementos exclusivos apenas uma vez a cada 1.0851831788708547256608362340568947172111832359638926... × 10^20tentativa.
Jedi de
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.