问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

抽奖案例一:按设置的概率实时抽奖-保证人人平等

创作时间:
作者:
@小白创作中心

抽奖案例一:按设置的概率实时抽奖-保证人人平等

引用
CSDN
1.
https://blog.csdn.net/weixin_43491743/article/details/145011572

抽奖案例一:按设置的概率实时抽奖-保证人人平等

核心:保证每一次抽奖的概率是一样的,这是一个放回问题(学过概率的朋友们都懂的)

一、奖品配置(本期不含库存,库存下期再讲)

比如我们想要这样配置奖品和概率:

奖品名称
中奖概率
汤臣一品
80%
迈巴赫
1%
1元红包
0.01%

用X轴描述一下我们的奖品分布:

为什么max值是10000呢?因为我们允许小数点后面最多两位,调整成整数后就是10000。

二、随机值与概率

从1-10000中间随机获取一个x值,就可以得到对应f(x)的结果。这个结果就是我们的中奖结果。

接下来就是用Java来实现这个f(x)。

入参:一个随机值x,一个奖品与概率的mapping关系

出参:f(x)结果

首先我们设置一下奖品和概率,这里直接放在Redis中:

[
{"id":1,"name":"汤臣一品","rate":"0.8"},
{"id":2,"name":"迈巴赫","rate":"0.01"},
{"id":3,"name":"1元红包","rate":"0.0001"}
]

随机数的话我们用SecureRandom,为什么不用Random?

SecureRandom secureRandom = new SecureRandom();
double v = secureRandom.nextDouble();
double ceil = Math.ceil(v * 10000); //等会就用这个随机数来抽奖

三、实现方式

把奖池数据处理成一个TreeMap,为什么要用TreeMap呢?等会儿我跟你讲

private TreeMap<Double, Integer> getTreeMap(String poolId) {
    //获取奖池中奖品信息
    List<Prize> prizes = getPool(poolId);
    
    if (CollectionUtils.isEmpty(prizes)) {
        return null;
    }
    TreeMap<Double, Integer> treeMap = new TreeMap<>();
    Double js = 0.0;
    for (int i = 0; i < prizes.size(); i++) {
        double rate = Math.ceil((Double.valueOf(prizes.get(i).getRate()))* 10000);
        treeMap.put(rate + js, prizes.get(i).getId());
        js = js + rate;
    }
    //收个尾
    treeMap.put(10000.0, 0);
    return treeMap;
}

会得到-----------【手动加粗放大】

{8000.0:1,8100.0:2,8101.0:3,10000.0:0}

再使用tailMap()和firstEntry()去找到对应的value

Integer prizeId = treeMap.tailMap(ceil, false).firstEntry().getValue();

就实现了根据概率抽奖的核心流程,是不是很简单

四、插播----冷知识

插播一段:为什么使用的是TreeMap而不是FlowerMap不是GlassMap

与HashMap相比,TreeMap是一个能比较元素大小的Map集合,TreeMap是SortedMap接口的一个实现。看的出来sorted,我该怎么跟你解释这个sorted呢?sorted?这个应该很好理解吧?S-O-R-T-E-D

就是排序的(因为TreeMap的key是要实现Comparable接口的,不然的话用不了哦)。所以能很好的支持我们这个f(x)并且快速找到结果。然后他也实现了NavigableMap,这篇(https://blog.csdn.net/qq_20919883/article/details/135370278)就给大家很好的解释了一下SortedMap和NavigableMap。看完记得回来继续啊!!!

我们使用到的tailMap

public NavigableMap<K,V> tailMap(K fromKey, boolean inclusive)

其中有两个参数:

fromKey 范围为 [fromKey, +∞) / (fromKey, +∞) 的子视图

inclusive 就是要不要包含fromKey。为true的时候就是[fromKey, +∞) ,为false时是(fromKey, +∞)

这样只是获得了一个子视图,比如我们随机数ceil是8002的话,子视图是8002至+∞。这里对应还有很多value,我们想要的只是:8002往后遇到的第一个键对应的值。所以结合一下firstEntry()这个方法的作用是返回这个映射中最小键(即第一个键)所对应的键值对

所以以下代码段就实现了我们的随机并保证概率的一次抽奖。

Integer prizeId = treeMap.tailMap(ceil, false).firstEntry().getValue();

关于inclusive的取值,是这样想的👇🏻

有这样一个问题:如果随机数=8000,你希望中的是汤臣一品还是迈巴赫?

(小孩子才做选择,成年的我都要!!!)

醒醒啊!很明显啊 ,应该是迈巴赫的。0-7999有8000个整数,这样才能对应我们一开始的权重分布图。如果随机数8000算汤臣一品的话,那阿汤的概率就变成80.01%了。

—> 因此inclusive取值是false才能保证,随机到8000时,抽到的是阿迈。

五、测试结果

奖品名称
10人抽奖结果
100人抽奖结果
1000人抽奖结果
10000人抽奖结果
汤臣一品
10
75
759
8037
迈巴赫
0
0
15
106
1元红包
0
0
0
3
未中奖
0
25
190
1853

就是这样,再见

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号