Как мне создать список случайных чисел без дубликатов?


110

Я пробовал использовать random.randint(0, 100), но некоторые цифры совпали. Есть ли метод / модуль для создания списка уникальных случайных чисел?

Примечание. Следующий код основан на ответе и был добавлен после публикации ответа. Это не часть вопроса; это решение.

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
Если они уникальны, они могут быть действительно случайными в правильном контексте. Как и случайная выборка индексов без замены может быть полностью случайной.
gbtimmon

Ответы:


181

Это вернет список из 10 номеров, выбранных из диапазона от 0 до 99, без дубликатов.

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

Что касается вашего конкретного примера кода, вы, вероятно, захотите прочитать все строки из файла один раз, а затем выбрать случайные строки из сохраненного списка в памяти. Например:

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

Таким образом, вам нужно фактически прочитать из файла только один раз перед циклом. Это сделать гораздо эффективнее, чем возвращаться к началу файла и f1.readlines()снова вызывать его для каждой итерации цикла.


2
Этот метод расходует память, особенно для больших образцов. Ниже я разместил код для гораздо большего объема памяти и эффективного решения, в котором используется линейный конгруэнтный генератор.
Thomas Lux

Мне было указано, что метод LCG менее «случайный», поэтому, если вы хотите сгенерировать много уникальных случайных последовательностей, разнообразие будет меньше, чем это решение. Если вам нужно всего лишь несколько случайных последовательностей, LCG - это то, что вам нужно!
Thomas Lux

Спасибо, Грег, это было полезно
Н Сиварам

15

Вы можете использовать функцию перемешивания из случайного модуля следующим образом:

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

Обратите внимание, что метод перемешивания не возвращает никакого списка, как можно было бы ожидать, он только перемешивает список, переданный по ссылке.


Здесь стоит упомянуть, что xrange работает только в Python 2, а не в Python 3.
Шаян Шафик,

10

Вы можете сначала создать список чисел от aдо b, где aи bявляются соответственно наименьшим и наибольшим числами в вашем списке, а затем перемешать его с помощью алгоритма Фишера-Йейтса или метода Python random.shuffle.


1
Создание полного списка индексов - пустая трата памяти, особенно для больших выборок. Ниже я разместил код для гораздо большего объема памяти и эффективного решения, в котором используется линейный конгруэнтный генератор.
Thomas Lux

8

Решение, представленное в этом ответе, работает, но может стать проблематичным с памятью, если размер выборки небольшой, а совокупность огромна (например random.sample(insanelyLargeNumber, 10)).

Чтобы исправить это, я бы пошел с этим:

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

Теперь random.sampleэтот подход используется для небольшого количества выборок из большой совокупности, поэтому проблема с памятью больше не существует. Хотя в то время, когда был написан этот ответ, реализация random.shuffleмогла быть другой.
Кирилл

5

Генератор линейных конгруэнтных псевдослучайных чисел

O (1) Память

O (k) Операции

Эта проблема может быть решена с помощью простого линейного конгруэнтного генератора . Это требует постоянных накладных расходов на память (8 целых чисел) и не более 2 * (длина последовательности) вычислений.

Все остальные решения используют больше памяти и больше вычислений! Если вам нужно всего несколько случайных последовательностей, этот метод будет значительно дешевле. Для диапазонов размера N, если вы хотите сгенерировать порядок Nуникальных k-последовательностей или более, я рекомендую принятое решение с использованием встроенных методов, random.sample(range(N),k)поскольку оно было оптимизировано в python для скорости.

Код

# 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

использование

Использование этой функции "random_range" такое же, как и для любого генератора (например, "диапазон"). Пример:

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

Образцы результатов

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
Это очень круто! Но я совершенно уверен, что это действительно отвечает на вопрос; скажем, я хочу выбрать 2 значения от 0 до 4. Без создания моего собственного prime, функция вернет мне только 4 возможных ответа, потому что valueэто единственная случайно выбранная вещь с 4 возможными значениями, когда нам нужно как минимум (4 выберите 2) = 6 (с учетом неслучайного порядка). random_range(2,4)вернет значения {(1, 0), (3, 2), (2, 1), (0, 3)}, но никогда не вернет пару (3,1) (или (1,3)). Ожидаете ли вы новых случайным образом генерируемых больших простых чисел при каждом вызове функции?
wowserx

1
(Также я предполагаю, что вы ожидаете, что люди будут перетасовать последовательность после того, как ваша функция вернет ее, если они хотят случайный порядок, поскольку вместо этого random_range(v)возвращаются к vуникальным последовательностям v!)
wowserx

Полностью верно! Трудно найти баланс между предотвращением целочисленного переполнения и генерацией достаточного количества случайных последовательностей. Я обновил функцию, добавив немного больше случайности, но она все еще не такая случайная, как v !. Это зависит от того, хотите ли вы использовать функцию несколько раз. Это решение лучше всего использовать, когда вы генерируете из большого диапазона значений (когда потребление памяти другими будет намного выше). Я еще подумаю, спасибо!
Thomas Lux

4

Если список из N чисел от 1 до N генерируется случайным образом, то да, есть вероятность того, что некоторые числа могут повторяться.

Если вам нужен список чисел от 1 до N в случайном порядке, заполните массив целыми числами от 1 до N, а затем используйте перемешивание Фишера-Ятса или Python random.shuffle().


3

Если вам нужно выбрать очень большие числа, вы не можете использовать range

random.sample(range(10000000000000000000000000000000), 10)

потому что бросает:

OverflowError: Python int too large to convert to C ssize_t

Кроме того, если random.sampleвы не можете произвести необходимое количество предметов из-за слишком малого диапазона

 random.sample(range(2), 1000)

бросает:

 ValueError: Sample larger than population

Эта функция решает обе проблемы:

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

Использование с очень большими числами:

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

Результат образца:

7822019936001013053229712669368
6289033704329783896566642145909
2473484300603494430244265004275
5842266362922067540967510912174
6775107889200427514968714189847
9674137095837778645652621150351
9969632214348349234653730196586
1397846105816635294077965449171
3911263633583030536971422042360
9864578596169364050929858013943

Использование, когда диапазон меньше количества запрошенных элементов:

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

Результат образца:

2, 0, 1

Он также работает с отрицательными диапазонами и шагами:

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

Примеры результатов:

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

что, если вы сгенерируете более 8 миллиардов чисел, рано или поздно увиденное станет слишком большим
david_adler

Этот ответ имеет серьезный недостаток для больших выборок. Вероятность столкновения линейно растет с каждым шагом. Я опубликовал решение с использованием линейного конгруэнтного генератора, который имеет O (1) накладные расходы на память и O (k) шагов, необходимых для генерации k чисел. Это можно решить намного эффективнее!
Томас Lux

Этот ответ определенно лучше, если вы хотите сгенерировать несколько случайных последовательностей порядка длины последовательности! Когда дело доходит до генерации нескольких уникальных последовательностей, метод LCG менее «случайен».
Thomas Lux

«Эта функция решает обе проблемы» Как решает вторую проблему? Вы по-прежнему не можете взять 1000 проб из популяции, состоящей из двух человек. Вместо создания исключения вы выдаете неверный результат; это вряд ли решение «проблемы» (что на самом деле не является проблемой для начала, поскольку совершенно неразумно запрашивать k уникальных выборок из совокупности n <k ).
Кирилл

1

Вы можете использовать библиотеку Numpy для быстрого ответа, как показано ниже -

Данный фрагмент кода перечисляет 6 уникальных чисел в диапазоне от 0 до 5. Вы можете настроить параметры для вашего удобства.

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

Вывод

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

Он не накладывает никаких ограничений, как мы видим в random.sample, как указано здесь .

Надеюсь, что это помогает немного.


1

Приведенный здесь ответ очень хорошо работает как в отношении времени, так и в отношении памяти, но немного сложнее, поскольку он использует расширенные конструкции python, такие как yield. Более простой ответ хорошо работает на практике, но проблема с этим ответом заключается в том, что он может генерировать множество ложных целых чисел до фактического построения требуемого набора. Попробуйте это сделать с PopulationSize = 1000, sampleSize = 999. Теоретически есть шанс, что он не завершится.

В приведенном ниже ответе рассматриваются обе проблемы, поскольку он детерминирован и в некоторой степени эффективен, хотя в настоящее время не так эффективен, как два других.

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

где функции getElem, percolateUp определены ниже

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

Наконец, время в среднем составляло около 15 мс для большого значения n, как показано ниже.

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]

Вы думаете, что ответ сложен? Что это тогда ?! А есть другой ответ , который генерирует множество «ложных целых чисел». Я запустил вашу реализацию с использованием предоставленного вами примера ввода (PopulationSize = 1000, sampleSize = 999). Ваша версия вызывает random.randintфункцию 3996 раз, а другая - cca. 6000 раз. Не такое уж большое улучшение, да?
Кирилл

@kyrill, ваш взгляд на этот ответ
aak318

1

Чтобы получить программу, которая генерирует список случайных значений без дубликатов, который является детерминированным, эффективным и построен с использованием базовых программных конструкций, рассмотрите функцию, extractSamplesопределенную ниже,

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

Основная идея состоит в том, чтобы отслеживать интервалы intervalLstдля возможных значений, из которых можно выбрать необходимые элементы. Это детерминировано в том смысле, что мы гарантированно сгенерируем выборку за фиксированное количество шагов (зависит исключительно от populationSizeи sampleSize).

Чтобы использовать указанную выше функцию для создания необходимого списка,

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

Мы также можем сравнить с более ранним решением (для более низкого значения PopulationSize)

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

Обратите внимание, что я уменьшил populationSizeзначение, поскольку он вызывает ошибку памяти для более высоких значений при использовании random.sampleрешения (также упоминалось в предыдущих ответах здесь и здесь ). Для приведенных выше значений мы также можем заметить, что это extractSamplesпревосходит random.sampleподход.

PS: Хотя основной подход похож на мой предыдущий ответ , есть существенные изменения в реализации, а также в подходе с улучшением ясности.


0

Очень простая функция, которая также решает вашу проблему

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

Проблема с подходами на основе набора («если случайное значение в возвращаемых значениях, попробуйте еще раз») заключается в том, что их время выполнения не определено из-за коллизий (которые требуют еще одной итерации «попробуйте еще раз»), особенно когда возвращается большое количество случайных значений. из ассортимента.

Альтернативой, не подверженной этой недетерминированной среде выполнения, является следующее:

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
Добро пожаловать в Stackoverflow. Пожалуйста, объясните свой ответ, почему и как он решает проблему, чтобы другие могли легко понять ваш ответ.
Octobus

Хотя этот код может решить вопрос, в том числе объяснение того, как и почему это решает проблему, действительно поможет улучшить качество вашего сообщения и, вероятно, приведет к большему количеству голосов за. Помните, что вы отвечаете на вопрос для будущих читателей, а не только для человека, который задает его сейчас. Пожалуйста , измените свой ответ , чтобы добавить объяснения и дать указание о том , что применять ограничения и допущения. Из отзыва
двойной сигнал

-1

Если вы хотите убедиться, что добавляемые числа уникальны, вы можете использовать объект Set

если используется версия 2.7 или выше, или импортируйте модуль sets, если нет.

Как отмечали другие, это означает, что числа не являются действительно случайными.


-1

для выборки целых чисел без замены между minvalи 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]

с 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]

Зачем вам генерировать перестановку, возможно, большого количества элементов, а затем выбирать только первый n_samplesиз них? Что вы мотивируете этим подходом? Можете ли вы объяснить, в чем преимущества вашего подхода по сравнению с любым из большого количества существующих ответов (большинство из них были получены 8 лет назад)?
Кирилл

на самом деле мой ответ имеет такую ​​же сложность, как и другие популярные ответы, и быстрее, потому что он использует numpy. другие методы random.shuffle, получившие наибольшее количество голосов , используют Mersenne Twister, qhich намного медленнее, чем алгоритмы, предлагаемые numpy (и, вероятно, jax). numpy и jax позволяют использовать другие алгоритмы генерации случайных чисел. jax также позволяет jit-компиляцию и дифференциацию, что может быть полезно для стохастической дифференциации. также, что касается «возможно большого» массива, некоторые наиболее популярные ответы делают то же самое random.shuffle, и я не считаю это греховным в относительном или даже абсолютном смысле
grisaitis

1
Не уверен, что вы имеете в виду под « random.shuffleиспользует Mersenne Twister» - это тасование Фишера-Йетса, как упоминалось в нескольких ответах. Он имеет линейную временную сложность, поэтому он не может быть асимптотически медленнее, чем алгоритмы, предлагаемые любой другой библиотекой, numpy или иначе. Если numpy быстрее, это только потому, что он реализован в C, но это не гарантирует генерации огромной перестановки (которая может даже не поместиться в памяти) только для выбора из нее нескольких элементов. Нет ни одного ответа, кроме вашего, который бы это делал.
Кирилл

Приношу свои извинения, я прочитал, что python random использовал Mersenne Twister в качестве prng. У вас есть источник, чтобы я мог узнать больше о Фишере Йейтсе и его роли в random.shuffle?
grisaitis

Здесь уже есть две отдельные ссылки на Википедию с двумя отдельными ответами. Если Википедия не является для вас достаточно хорошим источником, в конце статьи есть 14 ссылок. А еще есть Google. Это помогает? Да, и randomмодуль написан на Python, так что вы можете легко просмотреть его исходный код (попробуйте random.__file__).
Кирилл

-3

Из интерфейса командной строки в win xp:

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

В Канаде проводится лотерея 6/49. Я просто оборачиваю приведенный выше код в lotto.bat и запускаю C:\home\lotto.batили просто C:\home\lotto.

Поскольку число random.randintчасто повторяется, я использую setс, range(7)а затем укорачиваю его до 6.

Иногда, если число повторяется более 2 раз, длина результирующего списка будет меньше 6.

РЕДАКТИРОВАТЬ: Однако random.sample(range(6,49),6)это правильный путь.


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

1
Не могли бы вы объяснить, как это позволяет избежать дублирования? Из этого дампа кода это не очевидно.
Тоби Спейт

Это не так. print len(result), len(set(result)). Можно ожидать, resultчто уникальные элементы будут присутствовать только один раз при каждой 1.0851831788708547256608362340568947172111832359638926... × 10^20попытке.
Джедаи
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.