Skip to content

Commit

Permalink
Add WeightedLookup
Browse files Browse the repository at this point in the history
  • Loading branch information
jaraco committed Aug 11, 2021
1 parent bff7144 commit 9aa8d23
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 0 deletions.
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
v3.4.0
======

Add ``WeightedLookup``.

v3.3.0
======

Expand Down
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ Highlights include:
- Least, Greatest: Objects that are always less than or greater than any other.
- pop_all: Return all items from the mutable sequence and remove them from that sequence.
- DictStack: A stack of dicts, great for sharing scopes.
- WeightedLookup: A specialized RangeMap for selecting an item by weights.
57 changes: 57 additions & 0 deletions jaraco/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import itertools
import copy
import functools
import random

from jaraco.classes.properties import NonDataProperty
import jaraco.text
Expand Down Expand Up @@ -984,3 +985,59 @@ def __missing__(self, key):

def freeze(self):
self._frozen = lambda key: self.default_factory()


class Accumulator:
def __init__(self, initial=0):
self.val = initial

def __call__(self, val):
self.val += val
return self.val


class WeightedLookup(RangeMap):
"""
Given parameters suitable for a dict representing keys
and a weighted proportion, return a RangeMap representing
spans of values proportial to the weights:
>>> even = WeightedLookup(a=1, b=1)
[0, 1) -> a
[1, 2) -> b
>>> lk = WeightedLookup(a=1, b=2)
[0, 1) -> a
[1, 3) -> b
>>> lk[.5]
'a'
>>> lk[1.5]
'b'
Adds ``.random()`` to select a random weighted value:
>>> lk.random() in ['a', 'b']
True
>>> choices = [lk.random() for x in range(1000)]
Statistically speaking, choices should be .5 a:b
>>> ratio = choices.count('a') / choices.count('b')
>>> .4 < ratio < .6
True
"""

def __init__(self, *args, **kwargs):
raw = dict(*args, **kwargs)

# allocate keys by weight
indexes = map(Accumulator(), raw.values())
super().__init__(zip(indexes, raw.keys()), key_match_comparator=operator.lt)

def random(self):
lower, upper = self.bounds()
selector = random.random() * upper
return self[selector]

0 comments on commit 9aa8d23

Please sign in to comment.