有一个抽奖应用,从所有参与的用户抽出K位中奖用户(K=奖品数量),且要根据每位用户拥有的抽奖码数量作为权重。
如假设有三个用户及他们的权重是:(1)、B (1), C(2)。希望抽到一个的概率为25%,抽到B的概率为25%,抽到C的概率为50%。
比较直观的做法是把两个C放到列表中抽选,如[A, B, C, C],使用Python内置的函数随机的。选择[A, B, C, C],这样C抽到的概率即为50%。
这个办法的问题是权重比较大的时候,浪费内存空间。
更一般的方法,是将所有权重加和4,然后从[0,4)区间里随机挑选一个值,将A, B, C占用不同大小的区间。(0,1)是,(1,2)是B,(2、4)是C .
使用Python的函数随机的。ranint(0, 3)或者int (random.random() * 4)均可产生0 - 3的随机整数R .判断R在哪个区间即选择哪个用户。
接下来是寻找随机数在哪个区间的方法,
一种方法是按顺序遍历列表并保存已遍历的元素权重综合年代,一旦年代大于R,就返回当前元素。
从运营商进口itemgetter 用户=[(' A ', 1), (' B ', 1), (' C ', 2)] 总=总和(地图(itemgetter(1),用户)) rnd=int (random.random () *) # 0 ~ 3 s=0 u, w用户: s +=w 如果s比;研发: 回报你 >之前不过这种方法的复杂度是O (N),因为要遍历所有的用户。
可以想到另外一种方法,先按顺序把累积加的权重排成列的表,然后对它使用二分法搜索,二分法复杂度降到O (logN)(除去其他的处理)
用户=[(' A ', 1), (' B ', 1), (' C ', 2)] cum_weights=列表(itertools.accumulate(地图(itemgetter(1),用户)))# (1、2、4) 总=cum_weights [1] rnd=int (random.random () *) # 0 ~ 3 你好=len (cum_weights) - 1 指数=平分。二等分(cum_weights rnd 0,嗨) 返回用户(指数)[0] >之前Python内置库随机的选择函数(3.6版本后有)即是如此实现,随机的。选择函数签名为随机的。选择(人口、重量=None, *, cum_weights=None, k=1)人口是待选列表,权重是各自的权重,cum_weights是可选的计算好的累加权重(两者选一),k是抽选数量(有回置抽选)。源码如下:
def选项(自我、人口、重量=None, *, cum_weights=None, k=1): ”“人口“返回一个k大小的列表元素与替代选择。 如果相对权重或累积重量不指定, 选择是用概率相等。 ”“” 随机=self.random 如果cum_weights没有: 如果重量没有: _int=int 总=len(人口) 返回(人口(_int(随机()*总)]我在范围(k)) cum_weights=列表(_itertools.accumulate(权重) elif权重不是没有: 提高TypeError(无法指定权重和累积重量) 如果len (cum_weights) !=len(人口): 提高ValueError('权重的数量人口的不匹配) 平分=_bisect.bisect 总=cum_weights [1] 你好=len (cum_weights) - 1 返回[人口[平分(cum_weights随机()*总,0,你好)) 因为我在范围(k)) >之前
因为Python内置的random.choices是有回置抽选,无回置抽选函数是random.sample,但该函数不能根据权重抽选(随机的。示例(人口、k))。
原生的随机的。样品可以抽选个多个元素但不影响原有的列表,其使用了两种算法实现,保证了各种情况均有良好的性能。(源码地址:random.sample)
第一种是部分洗牌,得到K个元素就返回。时间复杂度是O (N),不过需要复制原有的序列,增加内存使用。
结果=[所有]* k n=len(人口) 池=列表(人口)#不改变原有的序列 因为我在范围(k): j=int (random.random () * (n)) 结果池[k]=[j] 池[j]=[n-i-1] #池已选中的元素移走,后面未选中元素填上 返回结果 >之前而第二种是设置一个已选择的设置,多次随机抽选,如果抽中的元素在组内,就重新再抽,无需复制新的序列。当k相对n较小时,random.sample使用该算法,重复选择元素的概率较小。
选择=组() selected_add=选中。添加#加速方法访问 因为我在范围(k): j=int (random.random () * n) j在选择: j=int (random.random () * n) selected_add (j) 结果[j]=[j]人口 返回结果Python实现一个带权无回置随机抽选函数的方法