Ответ 1
Используйте выборку коллектора. Это очень простой алгоритм, который работает для любого N
.
Здесь является одной реализацией Python, а здесь является другой.
Я пытаюсь написать алгоритм, который бы выбирал N отдельных элементов из последовательности случайным образом, не зная заранее размера последовательности, и где было бы дорого обходить последовательность более одного раза. Например, элементы последовательности могут быть строками огромного файла.
Я нашел решение, когда N = 1 (то есть "выбрать ровно один элемент случайным образом из огромной последовательности"):
import random
items = range(1, 10) # Imagine this is a huge sequence of unknown length
count = 1
selected = None
for item in items:
if random.random() * count < 1:
selected = item
count += 1
Но как я могу добиться того же для других значений N (скажем, N = 3)?
Используйте выборку коллектора. Это очень простой алгоритм, который работает для любого N
.
Здесь является одной реализацией Python, а здесь является другой.
Если ваша последовательность достаточно коротка, чтобы читать ее в памяти и произвольно сортировать, это приемлемо, тогда простой подход состоял бы в том, чтобы просто использовать random.shuffle
:
import random
arr=[1,2,3,4]
# In-place shuffle
random.shuffle(arr)
# Take the first 2 elements of the now randomized array
print arr[0:2]
[1, 3]
В зависимости от типа вашей последовательности вам может потребоваться преобразовать его в список, вызвав на нем list(your_sequence)
, но это будет работать независимо от типов объектов в вашей последовательности.
Естественно, если вы не можете поместить свою последовательность в память, или требования к памяти или процессору этого подхода слишком высоки для вас, вам нужно будет использовать другое решение.
Самый простой, который я нашел, это этот ответ в SO:
import random
my_list = [1, 2, 3, 4, 5]
num_selections = 2
new_list = random.sample(my_list, num_selections)
# To preserve the order of the list, you could do:
randIndex = random.sample(range(len(my_list)), n_selections)
randIndex.sort()
new_list = [my_list[i] for i in randIndex]
Если у вас версия python 3. 6+, вы можете использовать выбор
from random import choices
items = range(1, 10)
new_items = choices(items, k = 3)
print(new_items)
[6, 3, 1]
@NPE верен, но связанные с ним реализации являются субоптимальными и не очень "питоновскими". Здесь лучшая реализация:
def sample(iterator, k):
"""
Samples k elements from an iterable object.
:param iterator: an object that is iterable
:param k: the number of items to sample
"""
# fill the reservoir to start
result = [next(iterator) for _ in range(k)]
n = k - 1
for item in iterator:
n += 1
s = random.randint(0, n)
if s < k:
result[s] = item
return result
Изменить. Как показано в @ panda -34, оригинальная версия была ошибочной, но не потому, что я использовал randint
vs randrange
. Проблема в том, что мое начальное значение для n
не учитывало того факта, что randint
включен на обоих концах диапазона. Учитывая это, проблема устранена. (Примечание: вы также можете использовать randrange
, поскольку оно включено в минимальное значение и исключает максимальное значение.)
Далее вы получите N случайных элементов из массива X
import random
list(map(lambda _: random.choice(X), range(N)))
Достаточно принять или отклонить каждый новый элемент только один раз, и, если вы его примете, выкиньте случайно выбранный старый элемент.
Предположим, что вы выбрали N элементов K случайным образом, и вы видите (K + 1)-й элемент. Примите его с вероятностью N/(K + 1) и его вероятности в порядке. Текущие предметы попали с вероятностью N/K и выбрасывались с вероятностью (N/(K + 1)) (1/N) = 1/(K + 1), поэтому выживать с вероятностью (N/K) ( K/(K + 1)) = N/(K + 1), поэтому их вероятности тоже ОК.
И да, я вижу, кто-то указал вам на выборку коллектора - это одно объяснение того, как это работает.
Как упомянуто в упомянутых работах по отбору проб коллектора. Другой вариант - генерировать случайное число для каждого числа, которое вы видите, и выбирать верхние k-числа.
Чтобы сделать это итеративно, сохраняйте кучу пар k (случайное число, число) и всякий раз, когда вы видите новую цифру в кучу, если она больше, чем наименьшее значение в куче.
Это был мой ответ на дублированный вопрос (закрытый до того, как я смог опубликовать), который был несколько связан ( "генерирование случайных чисел без каких-либо дубликатов" ). Поскольку это другой подход, чем другие ответы, я оставлю его здесь, если он предоставит дополнительную информацию.
from random import randint
random_nums = []
N = # whatever number of random numbers you want
r = # lower bound of number range
R = # upper bound of number range
x = 0
while x < N:
random_num = randint(r, R) # inclusive range
if random_num in random_nums:
continue
else:
random_nums.append(random_num)
x += 1
Причина цикла while в цикле for заключается в том, что он позволяет упростить реализацию непропускания в случайной генерации (т.е. если вы получите 3 дубликата, вы не получите номера N-3).