从未知长度的序列中随机选择N个项目

时间:2012-03-13 18:36:30

标签: python algorithm

我正在尝试编写一种算法,该算法可以随机地从序列中选择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)实现相同的目标呢?

10 个答案:

答案 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

Here是一个Python实现,here是另一个。

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