随机加权select

我有这样的数据:

d = ( (701, 1, 0.2), (701, 2, 0.3), (701, 3, 0.5), (702, 1, 0.2), (702, 2, 0.3), (703, 3, 0.5) ) 

其中(701,1,2.2)=(id1,id2,优先级)

有一个漂亮的方式来selectid2,如果我知道id1,使用优先级?

函数(701)应该返回:
1 – 20%的情况
2 – 30%
3 – 50%

百分比当然是粗糙的

为每个ID1生成一个累积分布函数:

 cdfs = defaultdict() for id1,id2,val in d: prevtotal = cdfs[id1][-1][0] newtotal = prevtotal + val cdfs[id1].append( (newtotal,id2) ) 

所以你会有

 cdfs = { 701 : [ (0.2,1), (0.5,2), (1.0,3) ], 702 : [ (0.2,1), (0.5,2) ], 703 : [ (0.5,3) ] } 

然后生成一个随机数并在列表中search它。

 def func(id1): max = cdfs[id1][-1][0] rand = random.random()*max for upper,id2 in cdfs[id1]: if upper>rand: return id2 return None 

意识到我的第一个答案在math上相当麻烦,我提出了一个新的想法。 我相信这里的algorithm类似于其他几个答案的algorithm,但是这个实现似乎符合这个问题的“漂亮”(如果这相当简单)的要求:

 def func(id): rnd = random() sum = 0 for row in d: if row[0] == id: sum = sum + row[2] if rnd < sum: return row[1] 

用OP的示例数据是这样的:

  • select0到1.0之间的随机数字
  • 如果数字< 0.2返回第一个元素
  • 否则,如果数字< 0.5返回第二个元素
  • 否则(如果数字< 1.0 )返回第三个元素

在随机模块上使用离散的均匀分布,然后对其进行分区:

例如,对于情况701,使用10个值的分配,对于2个值返回1,对于另一个3,返回2,对于另一个5返回3。

你可以使用足够的统一分布来构build任何发行版:)

如果您的百分比值不会比整个百分比值更精确,请使用随机数生成器生成一个数字0-99。

然后在你的function,使用(编程)的情况下,select正确的数字。 例如(清理这个):

如果701
  如果random_num <20
    返回1
  否则,如果随机数<50 //(20 + 30)
    返回2
  否则如果随机数<100 //(20 + 30 + 50)
    返回3
  其他
     //错误

一个非常快速的黑客:

 import random d = { 701: [(1,0.2),(2,0.3),(3,0.5)], 702: [(1,0.2),(2,0.3),(3,0.5)] } def func(value): possible_values=d[value] total=sum(p[-1] for p in possible_values) random_value=random.random() prob=possible_values[0][-1]/total index=1 while index<len(possible_values) and prob<random_value: prob+=possible_values[index][-1]/total index+=1 return possible_values[index-1][0] if __name__=='__main__': testcases=1000 cnt=[0,0,0] for case in xrange(testcases): answer=func(701) cnt[answer-1]+=1 for i in xrange(3): print "Got %d %f%% of the time"%(i+1,float(cnt[i])/testcases*100) 

这并不漂亮,但它是首先想到的,似乎按预期工作。

这样做是在区间[0,1)(使用random.random())获得一个随机值。 然后使用随机值是否落在区间[0,0.2),[0.2,0.5)或[0.5,1)中来确定要返回哪个值。

两个想法(为了使参数名称清晰起见,允许我用分开的选项和比率来说明它们,如果它们包装在一个元组中,则可以保存“zip”):

a)将权重非规范化以得到整数比率,然后将其放入与列表中一样多的副本中,并使用random.choice

 def choice_with_ratios(options, ratios): tmp = sum([[v]*n for v, n in zip(options, ratios)], []) return random.choice(tmp) 

b)使用归一化权重并开始总结,直到达到随机生成的统一值

 def choice_with_weights(options, weights): s = 0 r = random.random() for v, w in zip(options, weights): s += w if s >= r: break return v 

顺便说一句,如果第一个字段被用作关键字,你应该把它放在字典里,比如:

 d = { 701: ((1, 0.2), (2, 0.3), (3, 0.5), 702: ((1, 0.3), (2, 0.2), (3, 0.5) } 

您也可以为每个值创build一个100个元素的列表,然后让random.choice从一个种子列表中进行select,这个列表的成员以您想要的权重进行加载:

 import random from collections import defaultdict d = ( (701, 1, 0.2), (701, 2, 0.3), (701, 3, 0.5), (702, 1, 0.2), (702, 2, 0.3), (702, 3, 0.5) ) class WeightedLookup(object): def __init__(self, valueTupleList): self.valdict = defaultdict(list) for key, val, prob in valueTupleList: self.valdict[key] += [val]*(int)(prob*100) def __getitem__(self,key): return random.choice(self.valdict[key]) lookup = WeightedLookup(d) # test out our lookup distribution, sample it 100000 times res = { 1:0, 2:0, 3:0 } for i in range(100000): res[lookup[701]] += 1 # print how many times each value was returned for k in (1,2,3): print k, res[k] 

打印:

 1 20059 2 30084 3 49857