如何从Python的Counter类中获得加权随机选择?

时间:2012-01-31 18:07:08

标签: python random iterator counter weighted

我有一个程序,我使用collections.Counter跟踪各种事物的成功 - thing 的每次成功都会增加相应的计数器:

import collections
scoreboard = collections.Counter()

if test(thing):
    scoreboard[thing]+ = 1

然后,为了将来的测试,我想倾向于那些已经取得最大成功的 thing Counter.elements()似乎是理想的,因为它返回的元素(以任意顺序)重复多次等于计数。所以我想我可以这样做:

import random
nextthing=random.choice(scoreboard.elements())

但不,那引发 TypeError:'itertools.chain'类型的对象没有len()。好的,random.choice can't work with iterators。但是,在这种情况下,长度是已知的(或可知的) - 它是sum(scoreboard.values())

我知道迭代未知长度列表并随机选择元素的基本算法,但我怀疑有更优雅的东西。我该怎么办?

6 个答案:

答案 0 :(得分:8)

使用itertools.islice获取可迭代的第N个项目,您可以相当轻松地执行此操作:

>>> import random
>>> import itertools
>>> import collections
>>> c = collections.Counter({'a': 2, 'b': 1})
>>> i = random.randrange(sum(c.values()))
>>> next(itertools.islice(c.elements(), i, None))
'a'

答案 1 :(得分:4)

您可以在list()中包装迭代器,将其转换为random.choice()的列表:

nextthing = random.choice(list(scoreboard.elements()))

这里的缺点是,这会扩展内存中的列表,而不是像通常使用迭代器那样逐项访问它。

如果你想迭代地解决这个问题,this algorithm可能是个不错的选择。

答案 2 :(得分:3)

以下内容将获得一个随机项目,其中分数是返回该项目的频率的权重。

import random

def get_random_item_weighted(scoreboard):    
    total_scoreboard_value = sum(scoreboard.values())

    item_loc = random.random() * total_scoreboard_value
    current_loc = 0
    for item, score in scoreboard.items():
        current_loc += score
        if current_loc > item_loc:
            return item

例如,如果有2个项目:

item1的得分为5
item2的得分为10

item2的返回次数是item1的两倍

答案 3 :(得分:1)

迭代的另一种变体:

import collections
from collections import Counter
import random


class CounterElementsRandomAccess(collections.Sequence):
    def __init__(self, counter):
        self._counter = counter

    def __len__(self):
        return sum(self._counter.values())

    def __getitem__(self, item):
        for i, el in enumerate(self._counter.elements()):
            if i == item:
                return el

scoreboard = Counter('AAAASDFQWERQWEQWREAAAAABBBBCCDDVBSDF')
score_elements = CounterElementsRandomAccess(scoreboard)
for i in range(10):
    print random.choice(score_elements)

答案 4 :(得分:1)

另一种变体, 安装有点麻烦,但查找是对数复杂的(适用于需要多次查找时):

import itertools
import random
from collections import Counter
from bisect import bisect

counter = Counter({"a": 5, "b": 1, "c": 1})

#setup
most_common = counter.most_common()
accumulated = list(itertools.accumulate([x[1] for x in most_common])) # i.e. [5, 6, 7]
total_size = accumulated[-1]

# lookup
i = random.randrange(total_size)
print(most_common[bisect(accumulated, i)])

答案 5 :(得分:1)

这里有很多过时的答案。

给出具有相应相对概率的选择字典(在您的情况下可以算),您可以使用Python 3.6中添加的新的 random.choices ,如下所示:

add_filter( 'woocommerce_quantity_input_min','woocommerce_quantity_input_min_callback', 10, 2 );
function woocommerce_quantity_input_min_callback( $min, $product ) {
    $min = 6;  
    return $min;
}