How can I get a weighted random pick from Python's Counter class?

You can do this rather easily by using itertools.islice to get the Nth item of an iterable:

>>> 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'

Given a dictionary of choices with corresponding relative probabilities (can be the count in your case), you can use the new random.choices added in Python 3.6 like so:

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]

For your code that uses Counter, you can do the same thing, because Counter also has the items() getter.

import collections
import random

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

Explanation: my_dict.items() is [('a', 1), ('b', 2), ('c', 3)].
So zip(*my_dict.items()) is [('a', 'b', 'c'), (1, 2, 3)].
And random.choices(('a', 'b', 'c'), (1, 2, 3)) is exactly what you want.


You could wrap the iterator in list() to convert it into a list for random.choice():

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

The downside here is that this expands the list in memory, rather than accessing it item-by-item as would normally get with an iterator.

If you wanted to solve this iteratively, this algorithm is probably a good choice.