16

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

import collections
scoreboard = collections.Counter()

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

然后,对于未来的测试,我想偏向那些产生最大成功的东西。Counter.elements()对此似乎很理想,因为它返回重复次数等于计数的元素(以任意顺序)。所以我想我可以这样做:

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

但是不,这会引发TypeError: object of type 'itertools.chain' has no len()。好的,所以random.choice不能使用 iterators。但是,在这种情况下,长度是已知的(或可知的)——它是sum(scoreboard.values()).

我知道遍历未知长度列表并随机选择一个元素的基本算法,但我怀疑还有更优雅的东西。我应该在这里做什么?

4

6 回答 6

7

您可以很容易地做到这一点,方法是使用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'
于 2012-01-31T19:06:58.183 回答
5

给定具有相应相对概率的选择字典(在您的情况下可以是计数),您可以random.choices像这样使用 Python 3.6 中添加的新选项:

import random

my_dict = {
    "choice a" : 1, # will in this case be chosen 1/3 of the time
    "choice b" : 2, # will in this case be chosen 2/3 of the time
}

choice = random.choices(*zip(*my_dict.items()))[0]

对于使用 的代码Counter,您可以做同样的事情,因为Counter也有items()getter。

import collections
import random

my_dict = collections.Counter(a=1, b=2, c=3)
choice = random.choices(*zip(*my_dict.items()))[0]

解释:my_dict.items()[('a', 1), ('b', 2), ('c', 3)]。也是
如此。 这正是您想要的。zip(*my_dict.items())[('a', 'b', 'c'), (1, 2, 3)]
random.choices(('a', 'b', 'c'), (1, 2, 3))

于 2019-02-02T16:16:16.877 回答
3

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

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

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

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

于 2012-01-31T18:12:16.863 回答
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 的两倍

于 2012-01-31T18:44:56.343 回答
2

另一种变体,Setup 有点麻烦,但是查找具有对数复杂度(适用于需要多次查找的情况):

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)])
于 2017-07-11T13:20:45.283 回答
0

另一个带有迭代的变体:

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)
于 2012-01-31T19:10:46.843 回答