抽奖算法逻辑

512 阅读4分钟

活动:在女神节当天,用户点击活动页面的抽奖按钮,可随机获得一张优惠券。 背景:由于经费限制,最多发放200张。并且要求不能集中到一个时间段发放。中奖时间平均要分布在上午09点-下午07点。

每次用户抽奖,系统需要一个算法,返回一个布尔值(是否中奖)。这个算法要保证两点: 十个小时下来,中奖时间分布要均匀; 中奖人数不能超过200

这个算法的主要难点在于:设计程序时,无法预知这一天将会有多少人参加活动,甚至他们将会在什么时间参加。如果可以预知这些,那么根据参加活动的总人数,很容易计算出来哪些人是中奖的,然后当这些人抽奖的时候,算法返回一个true就行了。但事实是我们都没有预知未来的能力。

为了解决这个问题,我们首先假定一个浮点类型的系数k作为每秒参加活动的人数,并将其存放在mongo中。活动开始后,再根据实时的热度随时调整到接近真实的数值(估算值)。我们暂且称这个系数为活动的“热度系数”。 k >= 1 :说明每秒有k个人访问 k < 1 :说明每隔1/k秒,有1个人访问

上面介绍过,活动有效期是10个小时。那么在活动期间,每个用户抽奖时,我们都可以得到一个活动剩余的时间t(单位:秒)。 所以: 剩余秒数*每秒参加活动人数=剩余参加活动人数(C) t * k = C

因为最多发放200张,所以我们必须有一个针对剩余优惠券的计数器,每次发出一张就减1。计数器的数字永远表示剩余多少张优惠券(M)可以发放。

所以当一个人来抽奖时,他中奖的概率就是: 剩余优惠券张数M / 剩余参加活动人数C 即:剩余优惠券张数M / (剩余秒数t * 每秒参加活动人数k) 换成公式的话,这个用户中奖的概率就是:M / ( t * k )

有了中奖的概率,我们可以在1至分母( t * k )之间,随机一个数字,如果这个数字小于等于分子(M),说明中奖了。

举个栗子:当一个用户抽奖时,假设: 剩余140张优惠券(M=140); 活动剩余4小时10分钟,换算成秒是15000秒(s=15000); 当前活动热度系数为0.5(k=0.5,代表目前每2秒有一个人参加活动)。 换言之,这个用户抽奖那一刻,还有140张券可以被抽中,但是他后面还有7500(15000 * 0.5)个用户等待抽奖,所以他的抽中的概率就是140/7500。模拟这个概率很简单,我们从 1 ~7500 中随机一个数字,如果得出一个小于等于140的数字,说明此用户中奖了,反之没有中奖。

这样一来,活动开始后,我们需要做的事情就是:根据实际情况,随时调整活动的热度系数(k)。热度系数变了,中奖的概率也就变了。

这样貌似大功告成了,但还有一个问题悬而未解:虽然这个算法尽量保证了中奖概率的分散性,但仍然无法完全保证中奖的时间可以平均分布在09点~19点之间。

为此我将200张优惠券分成10组,每组20张,正好对应这10个小时。然后把上面的算法应用在每个小时范围内。调整以下3处代码: 获取剩余秒数时,取当前小时剩余的秒数; 剩余张数计数器拆分成10个(20张/个),分别对应活动的10个小时; 获取剩余优惠券张数时,取当前小时剩余的优惠券;

换句话说,我将活动按小时细分成10个时间段,每个时间段平均发放20张,整体一天活动下来,保证了中奖时间的分散性。