我正在尝试编写一种算法,该算法可以随机地从序列中选择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)实现相同的目标呢?
答案 0 :(得分:68)
如果您的序列足够短以至于将其读入内存并随机排序是可以接受的,那么直接的方法就是使用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)
将其转换为列表,但无论序列中的对象类型如何,这都将有效。
当然,如果您无法将序列放入内存中,或者此方法的内存或CPU要求对您来说太高,则需要使用其他解决方案。
答案 1 :(得分:42)
使用reservoir sampling。这是一个非常简单的算法,适用于任何N
。
答案 2 :(得分:25)
最简单的我在SO中找到this回答:
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]
答案 3 :(得分:13)
如果你有3.6+的python版本,你可以使用选择
from random import choices
items = range(1, 10)
new_items = choices(items, k = 3)
print(new_items)
[6, 3, 1]
答案 4 :(得分:4)
@NPE是正确的,但是链接到的实现是次优的而不是非常&#34; pythonic&#34;。这是一个更好的实现:
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
,因为它包含最小值,并且不包含最大值。)
答案 5 :(得分:4)
以下将为您提供数组X中的N个随机项
import random
list(map(lambda _: random.choice(X), range(N)))
答案 6 :(得分:3)
仅仅接受或拒绝每个新项目就足够了,如果你接受它,就扔掉一个随机选择的旧项目。
假设您已随机选择了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)因此它们的概率也可以。
是的,我看到有人指出了油藏采样 - 这是对其运作方式的一种解释。
答案 7 :(得分:2)
正如aix提到的水库采样工程。另一个选项是为您看到的每个数字生成一个随机数,并选择前k个数字。
迭代地执行此操作,维护一堆k(随机数,数字)对,并且每当您看到一个新的数字插入到堆中时,如果它大于堆中的最小值。
答案 8 :(得分:0)
这是我对一个重复问题的回答(在我发布之前关闭),这有点相关(&#34;生成随机数而没有任何重复&#34;)。因为,这是一种与其他答案不同的方法,我将把它留在这里,以防它提供额外的见解。
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
for循环超过for循环的原因是它允许在随机生成中更容易实现非跳过(即如果你得到3个重复,你就不会得到N-3个数字)。
答案 9 :(得分:0)
numpy
库中有一个实现。
假设N
小于数组的长度,则必须执行以下操作:
# my_array is the array to be sampled from
assert N <= len(my_array)
indices = np.random.permutation(N) # Generates shuffled indices from 0 to N-1
sampled_array = my_array[indices]
如果您需要采样整个数组,而不仅仅是第一个N
位置,则可以使用:
import random
sampled_array = my_array[random.sample(len(my_array), N)]