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.