在电商促销或一些直播小游戏中,抽奖是一种比较常见的玩法。很多时候,展现给用户的可能是一个大转盘,如下:
通常我们会给出一堆奖品,每个奖品有各自的中奖概率,而且每个奖品可能还有库存的概念,就是说已经被抽完了的奖品是不再参与抽奖的。就拿陌陌里的这个大转盘来举例,奖品列表有爱心火箭、私人飞机、旋转木马、梦幻香水、口红、暖心奶茶、糖果、未中奖(未中奖可以看着是特殊的奖品)等8个奖品,每个奖品会根据其价值有不同的中奖概率,比如中糖果和暖心奶茶的概率高些,但中火箭的概率可能低很多。如果让你用java实现一个抽奖,你会怎么实现它呢?这正是我们今天要探讨的内容。
抽奖算法
在Java中,java.lang.Math.Random()方法本身基本可以保证大量测试的情况下避免高重复,且概率分布比较平均。所以我们根据奖品的概率列表基于java.lang.Math.Random()方法实现一个抽奖的工具类:
根据奖品的中奖概率列表probabilityList计算总概率sumRate;
根据总概率和各个奖品的概率对概率进行分块,得到分块列表blockList,比如传过来的概率列表是{0.25,0.15,0.6},那么分块区间就是0-0.25,0.25-0.4,0.4-1.0,blockList的集合就是{0.25,0.4,1.0};
根据java.lang.Math.Random()平均概率返回0到1之间数字的特性得到nextDouble;
把nextDouble的值按大小顺序插入到blockList中;
返回nextDouble插入的位置
抽奖逻辑
有了上面的抽奖算法,接下来我们分析下抽奖的逻辑是怎么实现的。为了更贴近使用场景,除了每个奖品有中奖概率外,增加一个库存的概念。因为有时候大奖并不是无限的,可能只有一个。在促销活动进行过程中,运营人员不一定一开始就把大奖的库存放出来,毕竟留在后面才更有悬念,如果大奖一放出来就被抽走了,用户的参与热情估计就没了,因为大家都是冲着大奖来的,就跟买彩票只想中500万一个道理。
定义一个奖品类Award,属性包含奖品名称、中奖概率、库存等。
接下来,我们模拟一下抽奖逻辑:
运行结果类似:
抽奖逻辑注意事项
上面的抽奖逻辑只是简单的单线程模拟抽奖算法的有效性,在生产环境中,还需要注意线程安全性、分布式事务等问题,抽奖都是高并发场景下,所以要处理好库存扣减以及库存扣减失败重抽等细节。