Smarter way of doing this?

John Hazen python-list at hazen.net
Mon Feb 2 17:50:41 EST 2004


* Max M <maxm at mxm.dk> [2004-02-02 05:15]:
> I have written this function, "choose_by_probability()"
> 
> It takes a list of probabilities in the range 0.00-1.00, and it must 
> then randomly select one of the probabilities and return it's index.
> 
> The idea is to have two lists, one with a value, and another with a 
> probability. The value then gets randomly selected from its probability.
> 
> values = ['item 1','item 2','item 3',]
> probabilities = [0.66, 0.33, 0.16]
> 
> print values[choose_by_probability(probabilities)]
> >>'item 1'
> 
> etc...
> 
> I just wondered if anybody has a better way of doing it, as this seems 
> nastily squared to me?

Well, since you're worried about the "nastily squared" aspect, and since
most (of my) applications for this only need to do the setup once, and
choose randomly multiple times, my solution uses a class to do this.
This takes into account someone else's suggestion of associating the
data and the probabilities directly, rather than worrying about indexes.

Here's my go at it:

----------chooser.py-----------
import random

class Item:
    def __init__(self,value,weight):
        self.value = value
        self.weight = weight
        self.probability = 0.0

class ProbabilityChooser:
    def __init__(self,items=None):
        """optional 'items' parameter is a sequence of 2-tuples.
           The first sub-item is the value, the second is the weight."""
        self.total_weight = 0.0
        self.items = []
        if items is not None:
            for (value,weight) in items:
                self.add_item(value,weight,recompute=False)
            self._compute_cumulative_probabilities()

    def _compute_cumulative_probabilities(self):
        """ use weights to pre-compute probabilities for all items """
        cumul = 0.0
        for item in self.items:
            cumul += item.weight / self.total_weight
            item.probability = cumul
            
    def add_item(self,value,weight=1,recompute=True):
        self.items.append(Item(value,weight))
        self.total_weight += weight
        if recompute:
            self._compute_cumulative_probabilities()

    def probabilities(self):
        return [(i.value,i.probability) for i in self.items]

    def choose(self):
        rand = random.random()
        for item in self.items:
            if rand <= item.probability:
                return item.value
----------/chooser.py-----------

And here's how I'd use it:

>>> import chooser
>>> values = ['item 1','item 2','item 3',]
>>> probabilities = [0.66, 0.33, 0.16]
>>> c = chooser.ProbabilityChooser(zip(values,probabilities))
>>> c.probabilities()
[('item 1', 0.57391304347826089), ('item 2', 0.86086956521739133),
('item 3', 1.0)]
>>> c.choose()
'item 1'
>>> c.choose()
'item 1'
>>> c.choose()
'item 2'
>>> c.choose()
'item 2'
>>> c.choose()
'item 1'
>>> c.choose()
'item 1'
>>> c.choose()
'item 3'
>>> c.choose()
'item 1'
>>> c.add_item('item 4',1.0)
>>> c.choose()
'item 4'
>>> c.choose()
'item 1'
>>> [c.choose() for i in range(30)]
['item 4', 'item 2', 'item 4', 'item 4', 'item 2', 'item 1', 'item 1',
'item 3', 'item 1', 'item 1', 'item 3', 'item 1', 'item 1', 'item 4',
'item 1', 'item 4', 'item 4', 'item 2', 'item 1', 'item 4', 'item 1',
'item 1', 'item 4', 'item 4', 'item 4', 'item 2', 'item 4', 'item 4',
'item 4', 'item 1']
>>> 

HTH-

John
-- 
John Hazen
<my first name>@<my last name>.net




More information about the Python-list mailing list