ソートされたリストがあります:(実際には単なる数字ではなく、複雑な時間のかかるアルゴリズムでソートされたオブジェクトのリストです)
mylist = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ,9 , 10 ]
N個のアイテムを提供するpython関数がありますが、順序は維持されますか?
例:
randomList = getRandom(mylist,4)
# randomList = [ 3 , 6 ,7 , 9 ]
randomList = getRandom(mylist,4)
# randomList = [ 1 , 2 , 4 , 8 ]
等...
次のコードは、サイズ4のランダムサンプルを生成します。
import random
sample_size = 4
sorted_sample = [
mylist[i] for i in sorted(random.sample(range(len(mylist)), sample_size))
]
(注:Python 2では、xrange
の代わりにrange
を使用することをお勧めします)
説明
random.sample(range(len(mylist)), sample_size)
元のリストのindicesのランダムサンプルを生成します。
これらのインデックスは、元のリストの要素の順序を保持するためにソートされます。
最後に、リストの内包表記では、サンプリングされたインデックスを指定して、元のリストから実際の要素を引き出します。
インデックスを置き換えずにランダムサンプルを取得し、インデックスを並べ替えて、元のインデックスから取得します。
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))]
または、数学のトリックを使用して、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を使いたいでしょう。)
edit:証明:母集団k
のサイズlen(seq)
からseq
のサブセットを選択する均一な分布(置換なし)を考慮する、任意のポイントi
でのパーティションを 'left'(0,1、...、i-1)および 'right'(i、i + 1、...、len(seq ))。左の既知のサブセットからnumbersPicked
を選択したことを考えると、残りは右の未知のサブセットの同じ均一な分布からのものでなければなりませんが、現在はパラメーターが異なります。特に、seq[i]
に選択された要素が含まれる確率は#remainingToChoose/#remainingToChooseFrom
または(k-numbersPicked)/(len(seq)-i)
であるため、それをシミュレートし、結果を再帰します。 (#remainingToChoose == #remainingToChooseFromの場合、残りの確率はすべて1であるため、これは終了する必要があります。)これは、偶然動的に生成される確率ツリーに似ています。基本的に、前の選択肢を条件付けることにより、一様な確率分布をシミュレートできます(確率ツリーを成長させると、現在のブランチの確率を、前の葉と同じように、つまり前の選択肢を条件として選択します。これは、この確率は一様に正確にN/kです)。
edit:Timothy Shieldsは Reservoir Sampling に言及しています。これは、len(seq)
が不明な場合(ジェネレータ式など)のこのメソッドの一般化です。具体的には、「アルゴリズムR」と表記されているものは、インプレースで実行される場合はO(N)およびO(1)スペースです。最初のN要素を取得し、それらをゆっくりと置き換えます(帰納的証明のヒントも示します)。また、ウィキペディアのページにあるリザーバーサンプリングの便利な分散バリアントとその他のバリアントもあります。
edit:以下に、より意味的に明白な方法でそれをコーディングする別の方法を示します。
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)
)
たぶん、インデックスのサンプルを生成し、リストからアイテムを収集することができます。
randIndex = random.sample(range(len(mylist)), sample_size)
randIndex.sort()
Rand = [mylist[i] for i in randIndex]
random.sample
はpython 2.3で導入されたようです
そのため、その下のバージョンでは、シャッフルを使用できます(4つのアイテムの例):
myRange = range(0,len(mylist))
shuffle(myRange)
coupons = [ bestCoupons[i] for i in sorted(myRange[:4]) ]
random.sampleが実装します。
>>> random.sample([1, 2, 3, 4, 5], 3) # Three samples without replacement
[4, 1, 5]