import math
import random
import time

class DiscreteRandomVariable(object):
        """ Simulates a Discrete Random Variable """
        def __init__(self, values):
                """ <values>: a list of (v, p) pairs where p is the relative
                    probability for the value v """
                self.dist = list() # the scaled distribution of the variable
                c = 0.0
                for v, p in values:
                        self.dist.append((v, c))
                        c += p
                self.ub = c # the upperbound of self.dist
                # we'll use binary search, which is simpler with a list of
                # length 2**n -- thus we'll fake the cneter of the list:
                self.vc = 2**int(math.ceil(math.log(len(self.dist),2)))/2
        
        def pick(self):
                """ picks a value accoriding to the given density """
                v = random.uniform(0, self.ub)
                d = self.dist
                c = self.vc-1
                s = self.vc
                while True:
                        s = s / 2
                        if s == 0:
                                break
                        if v <= d[c][1]:
                                c -= s
                        else:
                                c += s
                                # we only need this logic when increasing c
                                while len(d) <= c:
                                        s = s / 2
                                        c -= s
                                        if s == 0:
                                                break
                # we may have converged from the left, instead of the right
                if c == len(d) or v <= d[c][1]:
                        c -= 1
                return d[c][0]

class DiscreteRandomVariable_alt(object):
        """ Simulates a Discrete Random Variable alternatively"""
        def __init__(self, values):
                """ <values>: a list of (v, p) pairs where p is the relative
                    probability for the value v """
                if len(values)>0:
                        m = max([p for v,p in values])
                        assert(m!=0)
                        values = [(v,p/m) for v,p in values]
                self.values = values

        def pick(self):
                while True:
                        idx = random.randint(0,len(self.values)-1)
                        v, p = self.values[idx]
                        if p >= random.uniform(0, 1):
                                return v


if __name__=='__main__':
        # Curiously enought, the complexity of both algorithms seems to
        # be the same.  Is random.randint O(log(n)) !?
        ne = 100000
        nt = 10000
        values = [(random.uniform(0,10),random.uniform(0,10)) 
                        for i in xrange(ne)]
        drv = DiscreteRandomVariable(values)
        drva = DiscreteRandomVariable_alt(values)
        
        print "Starting.."
        bt = time.time()        
        for i in xrange(nt):
                drv.pick()
        print (time.time()-bt)/float(nt)

        bt = time.time()
        for i in xrange(nt):
                drva.pick()
        print (time.time()-bt)/float(nt)



