最初に O(N) 時間で追加の O(N) サイズのデータ構造を作成した後、O(1) 時間で置換を伴う加重ランダム選択を行うことができます。このアルゴリズムは、Walker と Vose によって開発されたAlias Methodに基づいています。これについては、こちらで詳しく説明しています。
本質的な考え方は、ヒストグラムの各ビンが一様な RNG によって確率 1/N で選択されるということです。そのため、それを詳しく説明し、過剰なヒットを受け取る人口の少ないビンについては、過剰なヒットを人口の多いビンに割り当てます。ビンごとに、それに属するヒットのパーセンテージと、超過分のパートナー ビンを保存します。このバージョンは、大小のビンを所定の位置で追跡し、追加のスタックの必要性を取り除きます。パートナーのインデックス ( に格納されてbucket[1]
いる) を、パートナーが既に処理されていることを示す指標として使用します。
これは、C 実装 hereに基づいた、最小限の Python 実装です。
def prep(weights):
data_sz = len(weights)
factor = data_sz/float(sum(weights))
data = [[w*factor, i] for i,w in enumerate(weights)]
big=0
while big<data_sz and data[big][0]<=1.0: big+=1
for small,bucket in enumerate(data):
if bucket[1] is not small: continue
excess = 1.0 - bucket[0]
while excess > 0:
if big==data_sz: break
bucket[1] = big
bucket = data[big]
bucket[0] -= excess
excess = 1.0 - bucket[0]
if (excess >= 0):
big+=1
while big<data_sz and data[big][0]<=1: big+=1
return data
def sample(data):
r=random.random()*len(data)
idx = int(r)
return data[idx][1] if r-idx > data[idx][0] else idx
使用例:
TRIALS=1000
weights = [20,1.5,9.8,10,15,10,15.5,10,8,.2];
samples = [0]*len(weights)
data = prep(weights)
for _ in range(int(sum(weights)*TRIALS)):
samples[sample(data)]+=1
result = [float(s)/TRIALS for s in samples]
err = [a-b for a,b in zip(result,weights)]
print(result)
print([round(e,5) for e in err])
print(sum([e*e for e in err]))