コーディングが簡単な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)-補助空間の方法
あるいは、数学のトリックを使用してmyList
、左から右に繰り返し実行し、動的に変化する確率で数値を選択することもでき(N-numbersPicked)/(total-numbersVisited)
ます。このアプローチの利点はO(N)
、ソートを含まないため、アルゴリズムであるということです。
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
概念実証と確率が正しいことのテスト:
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の因数で異なります。このテストを再度実行すると、順序が異なります。つまり、1つの順序に偏っていません。より少ないサンプルでテストを実行する[0,1,2,3,4], k=3
と、[0,1,2,3,4,5], k=4
同様の結果が得られました。
編集:なぜ人々が間違ったコメントに投票したり、投票することを恐れているのかわからない...いいえ、この方法に問題はありません。=)
(コメントのユーザーteganからの有用なメモ:これがpython2の場合、余分なスペースが本当に気になるのであれば、いつものようにxrangeを使用することをお勧めします。)
編集:証明:サイズのk
母集団からサブセットを選択する(置換なしの)一様分布を考慮すると、任意のポイントでの「左」(0,1、...、i-1)へのパーティションを考慮することができます。および「右」(i、i + 1、...、len(seq))。左側の既知のサブセットから選択したことを考えると、パラメーターは異なるものの、残りは右側の未知のサブセットの同じ一様分布に由来する必要があります。特に、選択された要素を含む確率は、、またはseq
len(seq)
i
numbersPicked
seq[i]
#remainingToChoose/#remainingToChooseFrom
(k-numbersPicked)/(len(seq)-i)
、それをシミュレートし、結果を繰り返します。(#remainingToChoose == #remainingToChooseFromの場合、残りのすべての確率は1であるため、これは終了する必要があります。)これは、動的に生成される確率ツリーに似ています。基本的に、事前の選択を条件として均一な確率分布をシミュレートできます(確率ツリーを成長させるときに、現在のブランチの確率を、以前の葉と同じように後方に選択します。つまり、以前の選択を条件とします。これは、次の理由で機能します。この確率は一様に正確にN / kです)。
編集:Timothy Shieldsは、Reservoir Samplingについて言及しています。これは、この方法を一般化したものです。len(seq)
不明な(ジェネレーター式など)。具体的には、「アルゴリズムR」として示されているのは、インプレースで実行された場合のO(N)およびO(1)スペースです。それは最初のN要素を取り、それらをゆっくりと置き換えることを含みます(帰納的証明のヒントも与えられます)。ウィキペディアのページには、貯水池サンプリングの便利な分散バリアントやその他のバリアントもあります。
編集:より意味的に明白な方法でそれを以下にコーディングする別の方法があります。
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
並べ替えてみませんか?