我(在 Python 2.7.9 中)编写了一个随机采样器生成器(索引),其速度仅取决于样本大小(它应该是 O(ns log(ns)),其中 ns 是样本大小)。因此,当样本量与总体规模相比较小时,它快速,因为它完全不依赖于总体规模。它不建立任何人口集合,它只是选择随机索引并在采样索引上使用一种二分法以避免重复并保持然后排序。给定一个可迭代的population,下面是如何使用itersample 生成器:
import random
sampler=itersample(len(population))
next_pick=sampler.next() # pick the next random (index of) element
或
import random
sampler=itersample(len(population))
sample=[]
for index in sampler:
# do something with (index of) picked element
sample.append(index) # build a sample
if some_condition: # stop sampling when needed
break
如果您需要实际元素而不仅仅是索引,只需在需要时将population iterable 应用于索引(第一个和第二个示例分别为population[sampler.next()] 和population[index])。
一些测试的结果表明速度并不取决于人口规模,所以如果你需要从 1000 亿人口中随机挑选 10 个元素,你只需支付 10 个(记住,我们事先不知道我们会选择多少个元素,否则你最好使用random.sample)。
Sampling 1000 from 1000000
Using itersample 0.0324 s
Sampling 1000 from 10000000
Using itersample 0.0304 s
Sampling 1000 from 100000000
Using itersample 0.0311 s
Sampling 1000 from 1000000000
Using itersample 0.0329 s
其他测试证实,运行时间与样本量的线性关系略大于线性:
Sampling 100 from 1000000000
Using itersample 0.0018 s
Sampling 1000 from 1000000000
Using itersample 0.0294 s
Sampling 10000 from 1000000000
Using itersample 0.4438 s
Sampling 100000 from 1000000000
Using itersample 8.8739 s
最后,这里是生成器函数itersample:
import random
def itersample(c): # c: population size
sampled=[]
def fsb(a,b): # free spaces before middle of interval a,b
fsb.idx=a+(b+1-a)/2
fsb.last=sampled[fsb.idx]-fsb.idx if len(sampled)>0 else 0
return fsb.last
while len(sampled)<c:
sample_index=random.randrange(c-len(sampled))
a,b=0,len(sampled)-1
if fsb(a,a)>sample_index:
yielding=sample_index
sampled.insert(0,yielding)
yield yielding
elif fsb(b,b)<sample_index+1:
yielding=len(sampled)+sample_index
sampled.insert(len(sampled),yielding)
yield yielding
else: # sample_index falls inside sampled list
while a+1<b:
if fsb(a,b)<sample_index+1:
a=fsb.idx
else:
b=fsb.idx
yielding=a+1+sample_index
sampled.insert(a+1,yielding)
yield yielding