I've designed a class ObjectPicker, thanObjectPicker that can be given Objectsobjects and weights. Objects can be picked randomly out of the ObjectPickerObjectPicker, with heavier weighted objects having a higher chance of being selected.
The pick() method is designed to be recursive and provide O(logN)\$O(\log n)\$ speed. What I'm not sure about is the defaulting that's currently in place. I'm wondering if there is a better way to handle it. However, I would appreciate any feedback you are willing to provide beyond the specific.
class ObjectPicker:
"""
An object that stores other objects with weights, and is able to
pick out one stored object, with a higher chance for higher weighted
objects.
"""
def __init__(self):
"""
Initialize the Object Picker object. Create the instance variable
bucket for storing objects.
"""
self.bucket = []
def add(self, item, weight=1):
"""
Add an item to the bucket, with given weight.
:param item: Object to be stored.
:param weight: Weight given to the object to determine odds.
"""
if self.bucket: # If there is anything in the bucket.
total = self.bucket[-1][-1] # Get the end number of the last item.
else: # If the bucket is empty.
total = 0 # End number is 0.
# Object stored in Tuple.
# (Object, Weight, Starting Num, Ending Num)
self.bucket.append((item, weight, total, total + weight))
def pick(self, choice=-1, storage=None):
"""
Pick an object from the bucket recursively,
taking weight into account.
:param choice: Number of choice.
:param storage: Storage Object to choose from.
"""
if not storage: # If no storage is given.
if not self.bucket: # If bucket is empty.
return None # Return None.
else:
storage = self.bucket # Storage is bucket
total = storage[-1][-1] # Get final weight.
if choice < 0: # If choice is < 0,
# Randomly choose a number to represent the choice.
choice = random.random() * total
# Start binary search in middle of storage object.
index = len(storage) // 2
start, end = storage[index][2:]
# If the choice is lower, recursively search left half.
if choice < start:
return self.pick(choice, storage[:index])
# If the choice is higher, recursively search right half.
elif choice > end:
return self.pick(choice, storage[index + 1:])
# Otherwise, choice is in number spread and return object.
else:
return storage[index][0]