web-dev-qa-db-ja.com

アイテムの順序を維持しながらリストからランダムなサンプルを取得しますか?

ソートされたリストがあります:(実際には単なる数字ではなく、複雑な時間のかかるアルゴリズムでソートされたオブジェクトのリストです)

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 ]

等...

79
Yochai Timmer

次のコードは、サイズ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のランダムサンプルを生成します。

これらのインデックスは、元のリストの要素の順序を保持するためにソートされます。

最後に、リストの内包表記では、サンプリングされたインデックスを指定して、元のリストから実際の要素を引き出します。

118
mhyfritz

コーディングが簡単な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を使いたいでしょう。)

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)

89
ninjagecko

たぶん、インデックスのサンプルを生成し、リストからアイテムを収集することができます。

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

random.sampleはpython 2.3で導入されたようです

そのため、その下のバージョンでは、シャッフルを使用できます(4つのアイテムの例):

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

random.sampleが実装します。

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