logo
首页
标签
关于

基于权重的随机选择

2022-09-28
算法

开发中经常会有需要随机选择某个物品的需求,如果是要求概率均等的,就很容易实现:

import random

items = ['A', 'B', 'C']
n = len(items)
i = random.randint(0, n - 1)
print(items[i])

或者直接使用random.choice:

print(random.choice(items))

但是如果要求每个元素有自己的权重呢?

  1. 可以先把所有的物品的权重都加起来,设为 s
  2. 随机出一个 0 到 s 之间的数,设为 c
  3. 遍历所有的物品,每次用 c 减去物品的权重,直至 c 小于 0,此时的物品就是随机的结果

用代码表示为:

def weighted_choice(items, weights):
    s = sum(weights)
    c = random.random() * s
    for i, w in enumerate(weights):
        c -= w
        if c < 0:
            return items[i]

还有一种方法是:

  1. 遍历每个物品,给每个物品维护一个权重的累加值,记为 cur_total,并存到一个列表中,记为 totals
  2. 然后随机出一个 0 到最大的 cur_total 之间的数字,记为 c
  3. 此时因为 totals 是有序的,可以用二分查找,找出 c 落在哪个位置

用代码表示为:

import random
import bisect

def weighted_choice(items, weights):
    totals = []
    cur_total = 0

    for w in weights:
        cur_total += w
        totals.append(cur_total)

    c = random.random() * cur_total
    i = bisect.bisect_right(totals, c)
    return items[i]

自从 python3.6 以后,标准库里直接加了一个函数 random.choices 用的就是第二种方法。

所以在 python 里之后就不用自己实现了,直接用标准库。如果用别的语言,可能还是会用到。

参考

  • https://stackoverflow.com/questions/3679694/a-weighted-version-of-random-choice
  • https://eli.thegreenplace.net/2010/01/22/weighted-random-generation-in-python/