たとえば、確率がパーセンテージにうまく収まる場合などにできるハックがあります。
たとえば、パーセンテージに問題がない場合は、次のように動作します (メモリのオーバーヘッドが高くなります)。
しかし、任意のフロート確率でそれを行う「本当の」方法は、構築後に累積分布からサンプリングすることです。これは、単位間隔 [0,1] を 'a'、'b'、および 'c' というラベルの付いた 3 つの線分に分割することと同じです。次に、単位間隔でランダムな点を選択し、それがどの線分かを確認します。
#!/usr/bin/python3
def randomCategory(probDict):
"""
>>> dist = {'a':.1, 'b':.2, 'c':.3, 'd':.4}
>>> [randomCategory(dist) for _ in range(5)]
['c', 'c', 'a', 'd', 'c']
>>> Counter(randomCategory(dist) for _ in range(10**5))
Counter({'d': 40127, 'c': 29975, 'b': 19873, 'a': 10025})
"""
r = random.random() # range: [0,1)
total = 0 # range: [0,1]
for value,prob in probDict.items():
total += prob
if total>r:
return value
raise Exception('distribution not normalized: {probs}'.format(probs=probDict))
確率が 0 であっても値を返すメソッドには注意する必要があります。幸い、このメソッドはそうではありませんが、念のため、 を挿入できif prob==0: continue
ます。
記録のために、これを行うためのハックな方法は次のとおりです。
import random
def makeSampler(probDict):
"""
>>> sampler = makeSampler({'a':0.3, 'b':0.4, 'c':0.3})
>>> sampler.sample()
'a'
>>> sampler.sample()
'c'
"""
oneHundredElements = sum(([val]*(prob*100) for val,prob in probDict.items()), [])
def sampler():
return random.choice(oneHundredElements)
return sampler
ただし、解像度の問題がない場合は、これがおそらく最速の方法です。=)