咱們平時比較多會遇到的一種情景是從一堆的數據中隨機選擇一個, 大多數咱們使用random
就夠了, 可是假如咱們要選取的這堆數據分別有本身的權重, 也就是他們被選擇的機率是不同的, 在這種狀況下, 就須要使用加權隨機
來處理這些數據html
下面是一種簡單的方案, 傳入權重的列表(weights
), 而後會返回隨機結果的索引值(index), 好比咱們傳入[2, 3, 5]
, 那麼就會隨機的返回0(機率0.2), 1(機率0.3), 2(機率0.5)python
簡單的思路就是把全部的權重加和, 而後隨機一個數, 看看落在哪一個區間算法
import random def weighted_choice(weights): totals = [] running_total = 0 for w in weights: running_total += w totals.append(running_total) rnd = random.random() * running_total for i, total in enumerate(totals): if rnd < total: return i
上面這個方法看起來很是簡單, 已經能夠完成咱們所要的加權隨機, 然是最後的這個for
循環貌似有些囉嗦, Python
有個內置方法bisect
能夠幫咱們加速這一步數組
import random import bisect def weighted_choice(weights): totals = [] running_total = 0 for w in weights: running_total += w totals.append(running_total) rnd = random.random() * running_total return bisect.bisect_right(totals, rnd)
bisect
方法能夠幫咱們查找rnd
在totals
裏面應該插入的位置, 兩個方法看起來差很少, 可是第二個會更快一些, 取決於weights
這個數組的長度, 若是長度大於1000, 大約會快30%左右app
其實在這個方法裏面totals
這個數組並非必要的, 咱們調整下策略, 就能夠判斷出weights
中的位置dom
def weighted_choice(weights): rnd = random.random() * sum(weights) for i, w in enumerate(weights): rnd -= w if rnd < 0: return i
這個方法比第二種方法居然快了一倍, 固然, 從算法角度角度, 複雜度是同樣的, 只不過咱們把賦值臨時變量的功夫省下來了, 其實若是傳進來的weights
是已經按照從大到小排序好的話, 速度會更快, 由於rnd遞減的速度最快(先減去最大的數).net
若是咱們使用同一個權重數組weights
, 可是要屢次獲得隨機結果, 屢次的調用weighted_choice
方法, totals
變量仍是有必要的, 提早計算好它, 每次獲取隨機數的消耗會變得小不少code
class WeightedRandomGenerator(object): def __init__(self, weights): self.totals = [] running_total = 0 for w in weights: running_total += w self.totals.append(running_total) def next(self): rnd = random.random() * self.totals[-1] return bisect.bisect_right(self.totals, rnd) def __call__(self): return self.next()
在調用次數超過1000次的時候, WeightedRandomGenerator
的速度是weighted_choice
的100倍htm
因此咱們在對同一組權重列表進行屢次計算的時候選擇方法4, 若是少於100次, 則使用方法3blog
在python3.2
以後, 提供了一個itertools.accumulate
方法, 能夠快速的給weights
求累積和
>>>> from itertools import accumulate >>>> data = [2, 3, 5, 10] >>>> list(accumulate(data)) [2, 5, 10, 20]
若是你有更好的方法, 歡迎在留言區討論