[Tutor] (No Subject)
Scott Widney
SWidney@ci.las-vegas.nv.us
Fri, 22 Feb 2002 10:14:30 -0800
> someone else suggested this with the disclaimer that it
> was not very efficient method and was untested:
>
> def weighted_choice(choices):
> tot = 0
> for w,v in choices:
> tot = tot + w
> d = random.random()*tot
> tot = 0
> for w,v in choices:
> tot = tot + w
> if tot > d:
> return v
>
I understand what this is doing, but I see that it's inefficient because it
iterates over the list at least once, possibly twice. It seemed to me that
there ought to be a way to rework this so that it only walks the list once.
This is what I came up with:
###
def weightedPick(weightedList, choice):
""" weightedList is of the form: [('symbol', weight), ...]
where 0.0 < weight < 1.0
"""
if len(weightedList) == 1:
return weightedList[0][0]
if choice < weightedList[0][1]:
return weightedList[0][0]
else:
return weightedPick(weightedList[1:],
(choice - weightedList[0][1]))
###
I pulled random() out of the function to make testing easier, but there are
probably other benefits as well. The function would probably be called this
way:
###
import random
wList = [('three', .3), ('one', .1), ('four', .4), ('two', .2)]
weighedPick(wList, random.random())
###
Some notes:
The function doesn't check to see if the sum of the weights =~ 1.0 (why
would you want anything else?). A Class that wraps the function and the data
could do that. For that matter, the function doesn't ensure that 'choice' is
between 0.0 and 1.0; but that's what random.random() returns. Again, I
suppose that's a job for the Class.
One other thing: I haven't had the chance to run this 1000 or so times to
check its accuracy. Does anyone have the time?
Scott