如何从Python';什么柜台?

如何从Python';什么柜台?,python,random,iterator,counter,weighted,Python,Random,Iterator,Counter,Weighted,我有一个程序,我使用集合跟踪各种事物的成功。计数器-事物的每次成功都会增加相应的计数器: import collections scoreboard = collections.Counter() if test(thing): scoreboard[thing]+ = 1 然后,对于未来的测试,我想偏向那些已经产生了最大成功的东西Counter.elements()似乎非常适合这样做,因为它返回重复次数等于计数的元素(以任意顺序)。所以我想我可以: import random ne

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

import collections
scoreboard = collections.Counter()

if test(thing):
    scoreboard[thing]+ = 1
然后,对于未来的测试,我想偏向那些已经产生了最大成功的东西
Counter.elements()
似乎非常适合这样做,因为它返回重复次数等于计数的元素(以任意顺序)。所以我想我可以:

import random
nextthing=random.choice(scoreboard.elements())
但是没有,这会引发类型错误:“itertools.chain”类型的对象没有len()。好吧,那么。但是,在本例中,长度是已知的(或可知的)-它是
sum(scoreboard.values())


我知道迭代未知长度列表并随机挑选元素的基本算法,但我怀疑还有更优雅的方法。我应该在这里做什么?

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

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


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

下面将得到一个随机项,其中分数是返回该项的频率的权重

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
例如,如果有两项:

第1项得分为5分
第2项得10分


返回item2的频率是item1的两倍。通过使用获取iterable的第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'

迭代的另一个变体:

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)
另一种变体, 设置有点麻烦,但查找的复杂性为对数(适用于需要多次查找的情况):


给定具有相应相对概率的选择字典(可以是您案例中的计数),您可以使用Python 3.6中添加的新选项,如下所示:

随机导入
我的字典={
“选择a”:1,在这种情况下,三分之一的时间会被选择
“选择b”:2,在这种情况下,三分之二的时间会被选择
}
choice=random.choices(*zip(*my_dict.items())[0]
对于使用
Counter
的代码,您可以做同样的事情,因为
Counter
也有
items()
getter

导入集合
随机输入
my_dict=collections.Counter(a=1,b=2,c=3)
choice=random.choices(*zip(*my_dict.items())[0]
说明:
mydict.items()
[('a',1),('b',2),('c',3)]

所以
zip(*my_dict.items())
[('a','b','c'),(1,2,3)]


random.选项(('a','b','c'),(1,2,3))
正是你想要的。

scoreboard.elements()
变成一个列表怎么样?@delnan-请看下面的评论。理想情况下,我希望避免将计数分解成一个巨大的列表。这样做否定了使用
计数器
的优势,而不是一开始就把所有东西都堆在一个大容器中。有没有一种方法可以直接计算项目,而不是遍历
i-1
元素?如果c的值很小,这不是问题,但是如果一个或多个键的计数很高,那么迭代将需要很长时间。正如@BrianMinton所暗示的,最坏情况下的运行时间与计数器中的计数之和成比例。如果计数较大,则速度会非常慢。对于非整数计数,这是无效的;对于非常大的计数,这是无效的,两者都是合法的。
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)
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)])