从列表中获取随机样品,同时保持项目的顺序?

我有一个sorting的列表,让我们说:(它不是真的只是数字,它是一个耗时的algorithmsorting的对象列表)

mylist = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ,9 , 10 ] 

有没有一些Python函数会给我N的项目,但会保持顺序?

例:

 randomList = getRandom(mylist,4) # randomList = [ 3 , 6 ,7 , 9 ] randomList = getRandom(mylist,4) # randomList = [ 1 , 2 , 4 , 8 ] 

等等…

以下代码将生成大小为4的随机样本。

 rand_smpl = [ mylist[i] for i in sorted(random.sample(xrange(len(mylist)), 4)) ] 

说明:

 random.sample(xrange(len(mylist)), sample_size) 

生成原始列表的索引的随机样本。

然后这个示例被sorting以保留原始列表中元素的sorting。

最后,列表理解从原始列表中抽取元素,给定抽样索引,并构build最终样本(实际元素)。

简单的代码O(N + K * log(K))方式

采取随机抽样而不更换指数,对指数进行分类,并从原始数据中提取。

 indices = random.sample(range(len(myList)), K) [myList[i] for i in sorted(indices)] 

或者更简洁:

 [x[1] for x in sorted(random.sample(enumerate(myList),K))] 

优化的O(N)时间,O(1) – 辅助空间的方式

您也可以使用math技巧,并myList迭代地通过myList ,以dynamic变化的概率(N-numbersPicked)/(total-numbersVisited)来挑选数字。 这种方法的优点是它是一个O(N)algorithm,因为它不涉及sorting!

 from __future__ import division def orderedSampleWithoutReplacement(seq, k): if not 0<=k<=len(seq): raise ValueError('Required that 0 <= sample_size <= population_size') numbersPicked = 0 for i,number in enumerate(seq): prob = (k-numbersPicked)/(len(seq)-i) if random.random() < prob: yield number numbersPicked += 1 

概念certificate和testing概率是正确的

在5个小时的过程中用1万亿个伪随机样本进行模拟:

 >>> Counter( tuple(orderedSampleWithoutReplacement([0,1,2,3], 2)) for _ in range(10**9) ) Counter({ (0, 3): 166680161, (1, 2): 166672608, (0, 2): 166669915, (2, 3): 166667390, (1, 3): 166660630, (0, 1): 166649296 }) 

概率与真实概率相差不到1.0001。 再次运行这个testing会导致不同的顺序,这意味着它不会偏向于一个顺序。 用[0,1,2,3,4], k=3[0,1,2,3,4,5], k=4较less样本运行testing的结果相似。

编辑:不知道为什么人们投票了错误的评论或害怕upvote …不,这种方法没有任何错误。 =)

(这也是用户tegan在注释中的一个有用的注释:如果这是python2,那么像往常一样,如果你真的关心额外的空间,你将会想要使用xrange。)

编辑 :certificate:考虑从大小为len(seq)的总体seq中挑选k个子集的均匀分布(没有replace),我们可以将任意点i处的分区视为“左”(0,1 ,. …,i-1)和“右”(i,i + 1,…,len(seq))。 鉴于我们从左侧已知子集中挑选了numbersPicked ,剩下的必须来自右侧未知子集上的相同均匀分布,尽pipe参数现在是不同的。 特别是, seq[i]包含一个select的元素的概率是(k-numbersPicked)/(len(seq)-i) #remainingToChoose/#remainingToChooseFrom(k-numbersPicked)/(len(seq)-i) ,所以我们模拟并输出结果。 (这必须终止,因为如果#remainingToChoose == #remainingToChooseFrom,那么所有剩余的概率都是1)。这与碰巧是dynamic生成的概率树类似。 基本上,你可以通过对先前select进行调节来模拟一致的概率分布(当你增长概率树时,你select当前分支的概率,使得它与先前的叶子相同,即以先前的select为条件;这是可行的,因为这个概率是一致的N / k)。

编辑 :Timothy Shields提到了Reservoir Sampling ,这是当len(seq)是未知的(例如使用一个生成器expression式)时这个方法的泛化。 具体来说,如果在原地完成,则被称为“algorithmR”的是O(N)和O(1)空间; 它涉及到第一个N元素,并慢慢地取代它们(也给出了一个暗示的归纳certificate)。 在维基百科页面上也可以find有用的分布式variables和各种各样的油藏采样。

编辑 :这是另一种在语义上更明显的方式下面的代码。

 from __future__ import division import random def orderedSampleWithoutReplacement(seq, sampleSize): totalElems = len(seq) if not 0<=sampleSize<=totalElems: raise ValueError('Required that 0 <= sample_size <= population_size') picksRemaining = sampleSize for elemsSeen,element in enumerate(seq): elemsRemaining = totalElems - elemsSeen prob = picksRemaining/elemsRemaining if random.random() < prob: yield element picksRemaining -= 1 from collections import Counter Counter( tuple(orderedSampleWithoutReplacement([0,1,2,3], 2)) for _ in range(10**5) 

显然random.sample是在Python 2.3中引入的

所以对于那个版本,我们可以使用shuffle(例如4项):

 myRange = range(0,len(mylist)) shuffle(myRange) coupons = [ bestCoupons[i] for i in sorted(myRange[:4]) ] 

也许你可以生成索引样本,然后从列表中收集项目。

 randIndex = random.sample(range(len(mylist)), sample_size) randIndex.sort() rand = [mylist[i] for i in randIndex] 

random.sample实现它。

 >>> random.sample([1, 2, 3, 4, 5], 3) # Three samples without replacement [4, 1, 5]