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),我如何才能实现相同的结果呢?
如果您的序列足够短,可以将其读取到内存中并随机排序,那么一个简单的方法就是只使用
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] |
号
根据序列的类型,您可能需要通过对其调用
当然,如果您不能将序列放入内存中,或者这种方法对内存或CPU的要求太高,那么您将需要使用不同的解决方案。
使用水库采样。这是一个非常简单的算法,适用于任何
这里是一个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指出原始版本有缺陷,但不是因为我使用的是
只接受或拒绝每个新项目一次就足够了,如果你接受了它,就扔掉一个随机选择的旧项目。
假设您随机选择了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个数字)。