Python加权随机

dor*_*emi 24 python algorithm round-robin

我需要根据加权循环法返回不同的值,以便20中的1得到A,20中的1得到B,其余的转到C.

所以:

A => 5%
B => 5%
C => 90%
Run Code Online (Sandbox Code Playgroud)

这是一个似乎有用的基本版本:

import random

x = random.randint(1, 100)

if x <= 5:
    return 'A'
elif x > 5 and x <= 10:
    return 'B'
else:
    return 'C'
Run Code Online (Sandbox Code Playgroud)

这个算法是否正确?如果是这样,可以改进吗?

jur*_*eza 49

你的算法是正确的,如何更优雅:

import random
my_list = ['A'] * 5 + ['B'] * 5 + ['C'] * 90
random.choice(my_list)
Run Code Online (Sandbox Code Playgroud)

  • +1,但另外,只要项目数保持成比例,就没有必要生成100个列表项.在这种情况下,你可以做'['A','B'] + ['C']*18`. (5认同)
  • 为什么不只是`my_list = ['A'] * 5 + ['B'] * 5 + ['C'] * 90`? (3认同)
  • @JoelCornett考虑到这一点,但我认为这种方式更具可读性.谢谢你的纠正. (3认同)
  • 在这种有限的情况下,这可能无关紧要,但这种方法通常在时间和空间方面都是低效的。更好的解决方案是在 [0,1) 中分配对应于权重比例的范围,例如,A 为 [0, 5/100),B 为 [5/100, 10/100),而 [10/100, 1)对于C,在处理诸如重复小数之类的事情时使用适当的近似值/四舍五入,然后使用random.random或random.uniform生成随机数。 (3认同)
  • 从 Python 3.6 开始,您可以使用 [random.choices](https://docs.python.org/3/library/random.html#random.choices)。如果您关心速度,那么接下来的两个答案会更快。 (2认同)

and*_*oke 32

没关系.更一般地说,您可以定义如下内容:

from collections import Counter
from random import randint

def weighted_random(pairs):
    total = sum(pair[0] for pair in pairs)
    r = randint(1, total)
    for (weight, value) in pairs:
        r -= weight
        if r <= 0: return value

results = Counter(weighted_random([(1,'a'),(1,'b'),(18,'c')])
                  for _ in range(20000))
print(results)
Run Code Online (Sandbox Code Playgroud)

这使

Counter({'c': 17954, 'b': 1039, 'a': 1007})
Run Code Online (Sandbox Code Playgroud)

如你所料,它接近18:1:1.

  • 它不是必需的 - 这样的算法递减每个条目的计数器,所以结果是相同的(统计上) (2认同)
  • 我认为这比公认的答案要好。我知道Python代码应该努力提高可读性,但是像扔掉内存一样疯狂。特别是当您在权重不断变化的模拟中使用此功能时。口译员将建立并清理许多清单 (2认同)

Hyp*_*eus 9

如果你想使用加权随机而不是百分位随机,你可以创建自己的Randomizer类:

import random

class WeightedRandomizer:
    def __init__ (self, weights):
        self.__max = .0
        self.__weights = []
        for value, weight in weights.items ():
            self.__max += weight
            self.__weights.append ( (self.__max, value) )

    def random (self):
        r = random.random () * self.__max
        for ceil, value in self.__weights:
            if ceil > r: return value

w = {'A': 1.0, 'B': 1.0, 'C': 18.0}
#or w = {'A': 5, 'B': 5, 'C': 90}
#or w = {'A': 1.0/18, 'B': 1.0/18, 'C': 1.0}
#or or or

wr = WeightedRandomizer (w)

results = {'A': 0, 'B': 0, 'C': 0}
for i in range (10000):
    results [wr.random () ] += 1

print ('After 10000 rounds the distribution is:')
print (results)
Run Code Online (Sandbox Code Playgroud)