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

Pick N items at random from sequence of unknown length

我正在尝试编写一种算法,它可以从序列中随机选取n个不同的项,而不预先知道序列的大小,以及在哪里多次迭代序列比较昂贵。例如,序列的元素可能是一个大文件的行。

当n=1时,我发现了一个解决方案(也就是说,当试图从一个巨大的序列中随机选取一个元素时):

1
2
3
4
5
6
7
8
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),我如何才能实现相同的结果呢?


如果您的序列足够短,可以将其读取到内存中并随机排序,那么一个简单的方法就是只使用random.shuffle

1
2
3
4
5
6
7
8
9
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的要求太高,那么您将需要使用不同的解决方案。


使用水库采样。这是一个非常简单的算法,适用于任何N

这里是一个Python实现,这里是另一个。


我找到的最简单的答案是:

1
2
3
4
5
6
7
8
9
10
11
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.6+的python版本,则可以使用选项

1
2
3
4
5
6
7
from random import choices

items = range(1, 10)
new_items = choices(items, k = 3)

print(new_items)
[6, 3, 1]


下面将给出数组x中的n个随机项

1
2
import random
list(map(lambda _: random.choice(X), range(N)))


@NPE是正确的,但是链接到的实现是次优的,并且不是非常"Python式"。下面是一个更好的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

edit as@panda-34指出原始版本有缺陷,但不是因为我使用的是randintrandrange。问题是,我对EDOCX1的初始值(0)并不能解释randint在范围两端都包含这一事实。考虑到这一点,解决了这个问题。(注:您也可以使用randrange,因为它包含在最小值上,不包含在最大值上。)


只接受或拒绝每个新项目一次就足够了,如果你接受了它,就扔掉一个随机选择的旧项目。

假设您随机选择了k的n个项,并且看到了(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(随机数、数字)对的堆,并且每当看到新的数字时,如果它大于堆中的最小值,就将它插入到堆中。


这是我对一个重复问题的回答(在我发帖之前就结束了),这有点关联("生成没有任何重复的随机数")。因为它与其他答案不同,所以我将把它留在这里,以防它提供更多的洞察力。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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循环上进行while循环的原因是,它允许在随机生成中更容易地实现非跳过(即,如果有3个重复项,则不会得到n-3个数字)。