我需要写一个加权版的random。选择(列表中的每个元素有不同的被选择的概率)。这是我想到的:
def weightedChoice(choices):
"""Like random.choice, but each element can have a different chance of
being selected.
choices can be any iterable containing iterables with two items each.
Technically, they can have more than two items, the rest will just be
ignored. The first item is the thing being chosen, the second item is
its weight. The weights can be any numeric values, what matters is the
relative differences between them.
"""
space = {}
current = 0
for choice, weight in choices:
if weight > 0:
space[current] = choice
current += weight
rand = random.uniform(0, current)
for key in sorted(space.keys() + [current]):
if rand < key:
return choice
choice = space[key]
return None
这个函数对我来说太复杂了,而且很丑。我希望这里的每个人都能提供一些改进的建议或其他方法。对我来说,效率没有代码的整洁和可读性重要。
如果你碰巧有Python 3,并且害怕安装numpy或编写自己的循环,你可以这样做:
import itertools, bisect, random
def weighted_choice(choices):
weights = list(zip(*choices))[1]
return choices[bisect.bisect(list(itertools.accumulate(weights)),
random.uniform(0, sum(weights)))][0]
因为你可以用一袋管道适配器做任何东西!尽管……我必须承认,尼德的回答虽然稍长一些,但比较容易理解。
在Udacity免费课程AI for Robotics中,Sebastien Thurn对此进行了演讲。基本上,他用mod运算符%做了一个权重索引的圆形数组,将变量beta设为0,随机选择一个索引,
for循环遍历N,其中N是指标的数量,在for循环中,首先按公式增加beta:
Beta = Beta +来自{0…2 * Weight_max}
然后在for循环中嵌套一个while循环per:
while w[index] < beta:
beta = beta - w[index]
index = index + 1
select p[index]
然后到下一个索引,根据概率(或课程中介绍的情况下的归一化概率)重新采样。
在Udacity上找到第8课,机器人人工智能的第21期视频,他正在讲粒子滤波器。
下面是Python 3.6标准库中包含的版本:
import itertools as _itertools
import bisect as _bisect
class Random36(random.Random):
"Show the code included in the Python 3.6 version of the Random class"
def choices(self, population, weights=None, *, cum_weights=None, k=1):
"""Return a k sized list of population elements chosen with replacement.
If the relative weights or cumulative weights are not specified,
the selections are made with equal probability.
"""
random = self.random
if cum_weights is None:
if weights is None:
_int = int
total = len(population)
return [population[_int(random() * total)] for i in range(k)]
cum_weights = list(_itertools.accumulate(weights))
elif weights is not None:
raise TypeError('Cannot specify both weights and cumulative weights')
if len(cum_weights) != len(population):
raise ValueError('The number of weights does not match the population')
bisect = _bisect.bisect
total = cum_weights[-1]
return [population[bisect(cum_weights, random() * total)] for i in range(k)]
来源:https://hg.python.org/cpython/file/tip/Lib/random.py l340
如果你碰巧有Python 3,并且害怕安装numpy或编写自己的循环,你可以这样做:
import itertools, bisect, random
def weighted_choice(choices):
weights = list(zip(*choices))[1]
return choices[bisect.bisect(list(itertools.accumulate(weights)),
random.uniform(0, sum(weights)))][0]
因为你可以用一袋管道适配器做任何东西!尽管……我必须承认,尼德的回答虽然稍长一些,但比较容易理解。
粗糙的,但可能足够:
import random
weighted_choice = lambda s : random.choice(sum(([v]*wt for v,wt in s),[]))
这有用吗?
# define choices and relative weights
choices = [("WHITE",90), ("RED",8), ("GREEN",2)]
# initialize tally dict
tally = dict.fromkeys(choices, 0)
# tally up 1000 weighted choices
for i in xrange(1000):
tally[weighted_choice(choices)] += 1
print tally.items()
打印:
[('WHITE', 904), ('GREEN', 22), ('RED', 74)]
假设所有权重都是整数。它们的和不一定是100,我这么做只是为了让测试结果更容易理解。(如果权重是浮点数,则将它们都乘以10,直到所有权重>= 1。)
weights = [.6, .2, .001, .199]
while any(w < 1.0 for w in weights):
weights = [w*10 for w in weights]
weights = map(int, weights)