Как я могу получить взвешенный случайный выбор из класса Counter Python? - PullRequest
12 голосов
/ 31 января 2012

У меня есть программа, в которой я отслеживаю успех различных вещей , используя collections.Counter - каждый успех вещи увеличивает соответствующий счетчик:

import collections
scoreboard = collections.Counter()

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

Затем, для будущих тестов, я хочу перейти к вещам , которые принесли наибольший успех.Counter.elements() казалось идеальным для этого, так как он возвращает элементы (в произвольном порядке), повторенные количество раз, равное количеству.Поэтому я решил, что могу просто сделать:

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

Но нет, это вызывает TypeError: объект типа 'itertools.chain' не имеет len () .Итак, random.choice не может работать с итераторами .Но в этом случае длина известна (или может быть известна) - это sum(scoreboard.values()).

Я знаю основной алгоритм для перебора списка неизвестной длины и честного выбора элемента наугад, но я подозреваю,что есть что-то более элегантное.Что мне здесь делать?

Ответы [ 6 ]

8 голосов
/ 31 января 2012

Вы можете сделать это довольно легко, используя 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'
4 голосов
/ 31 января 2012

Вы можете заключить итератор в list(), чтобы преобразовать его в список для random.choice():

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

Недостатком здесь является то, что это расширяет список в памяти, вместо того, чтобы обращаться к нему поэлементно, как это обычно получается с итератором.

Если вы хотите решить это итеративно, этот алгоритм , вероятно, является хорошим выбором.

3 голосов
/ 31 января 2012

Следующее получит случайный элемент, где оценка является весовым показателем того, как часто нужно возвращать этот элемент. Например,

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 элемента:

элемент1 имеетоценка 5
item2 имеет оценку 10

item2 будет возвращаться в два раза чаще, чем item1

1 голос
/ 02 февраля 2019

Лоты датированных ответов здесь.

Имея словарь вариантов выбора с соответствующими относительными вероятностями (в вашем случае это может быть число), вы можете использовать новый 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]
1 голос
/ 11 июля 2017

другой вариант, Настройка немного громоздка, но поиск имеет логарифмическую сложность (подходит, когда требуется несколько поисков):

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)])
1 голос
/ 31 января 2012

Другой вариант с итерацией:

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)
...