我有一个很长的python生成器,我想通过随机选择一个值的子集来“稀释”。不幸的是,random.sample()
不适用于任意迭代。显然,它需要支持len()
操作的东西(也许是对序列的非顺序访问,但这一点并不清楚)。而且我不想建立一个庞大的列表,所以我可以把它缩小。
事实上,可以在一次通过中从序列统一进行采样,而不知道它的长度 - Programming perl
中有一个很好的算法可以做到这一点(编辑:“水库采样”,谢谢@ user2357112!)。但有没有人知道提供此功能的标准python模块?
演示问题(Python 3)
>>> import itertools, random
>>> random.sample(iter("abcd"), 2)
...
TypeError: Population must be a sequence or set. For dicts, use list(d).
在Python 2上,错误更透明:
Traceback (most recent call last):
File "<pyshell#12>", line 1, in <module>
random.sample(iter("abcd"), 2)
File "/usr/local/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/random.py", line 321, in sample
n = len(population)
TypeError: object of type 'iterator' has no len()
如果除了random.sample()
之外别无选择,我会试着将生成器包装成一个提供__len__
方法的对象(我可以提前找出长度)。所以我会接受一个答案,说明如何干净利落地做到这一点。
答案 0 :(得分:8)
由于您知道iterable返回的数据的长度,因此您可以使用xrange()
在您的iterable中快速生成索引。然后你就可以运行iterable,直到你抓住了所有的数据:
import random
def sample(it, length, k):
indices = random.sample(xrange(length), k)
result = [None]*k
for index, datum in enumerate(it):
if index in indices:
result[indices.index(index)] = datum
return result
print sample(iter("abcd"), 4, 2)
在替代方案中,这里是使用&#34;算法R&#34;的实施例:
import random
def R(it, k):
'''https://en.wikipedia.org/wiki/Reservoir_sampling#Algorithm_R'''
it = iter(it)
result = []
for i, datum in enumerate(it):
if i < k:
result.append(datum)
else:
j = random.randint(0, i-1)
if j < k:
result[j] = datum
return result
print R(iter("abcd"), 2)
请注意,算法R不会为结果提供随机顺序。在给出的示例中,'b'
永远不会在结果中位于'a'
之前。
答案 1 :(得分:2)
使用O(n)
算法R https://en.wikipedia.org/wiki/Reservoir_sampling,从k
中选择iterable
个随机元素:
import itertools
import random
def reservoir_sample(iterable, k):
it = iter(iterable)
if not (k > 0):
raise ValueError("sample size must be positive")
sample = list(itertools.islice(it, k)) # fill the reservoir
random.shuffle(sample) # if number of items less then *k* then
# return all items in random order.
for i, item in enumerate(it, start=k+1):
j = random.randrange(i) # random [0..i)
if j < k:
sample[j] = item # replace item with gradually decreasing probability
return sample
示例:
>>> reservoir_sample(iter('abcdefghijklmnopqrstuvwxyz'), 5)
['w', 'i', 't', 'b', 'e']
reservoir_sample()
代码来自this answer。
答案 2 :(得分:1)
如果您需要具有固定频率的原始迭代器的子集(即,如果生成器生成10000个数字,您需要“统计”其中100个,如果它生成1000000个数字,则需要10000个 - 始终为1% ),你会将迭代器包装在一个构造中,产生内循环的结果,概率为1%。
所以我想你想要来自未知基数来源的固定数量样本,就像你提到的Perl算法一样。
你可以将迭代器包装在一个拥有自己的小内存的构造中,以便跟踪库,并以降低的概率循环它。
import random
def reservoir(iterator, size):
n = size
R = iterator[0:n]
for e in iterator:
j = random.randint(0, n-1)
n = n + 1
if (j < size):
R[j] = e
return R
所以
print reservoir(range(1, 1000), 3)
可能会打印出来
[656, 774, 828]
我已尝试如上所述生成一百万轮,并将三列的分布与此滤波器进行比较(我期望高斯分布)。
# get first column and clean it
python file.py | cut -f 1 -d " " | tr -cd "0-9\n" \
| sort | uniq -c | cut -b1-8 | tr -cd "0-9\n" | sort | uniq -c
虽然不是(还)真正的高斯,但对我来说看起来还不错。
答案 3 :(得分:0)
一种可能的方法是在迭代器周围构建一个生成器来选择随机元素:
def random_wrap(iterator, threshold):
for item in iterator:
if random.random() < threshold:
yield item
当您不知道长度并且迭代器的可能大小将是令人望而却步时,此方法将非常有用。请注意,保证最终列表的大小是有问题的。
一些示例运行:
>>> list(random_wrap(iter('abcdefghijklmnopqrstuvwxyz'), 0.25))
['f', 'h', 'i', 'r', 'w', 'x']
>>> list(random_wrap(iter('abcdefghijklmnopqrstuvwxyz'), 0.25))
['j', 'r', 's', 'u', 'x']
>>> list(random_wrap(iter('abcdefghijklmnopqrstuvwxyz'), 0.25))
['c', 'e', 'h', 'n', 'o', 'r', 'z']
>>> list(random_wrap(iter('abcdefghijklmnopqrstuvwxyz'), 0.25))
['b', 'c', 'e', 'h', 'j', 'p', 'r', 's', 'u', 'v', 'x']
答案 4 :(得分:0)
使用带有随机选择器功能的itertools.compress()
功能:
itertools.compress(long_sequence, (random.randint(0, 100) < 10 for x in itertools.repeat(1)))