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

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

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

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

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

本文介绍了一个抽奖系统的实现方案,重点讨论了如何通过设置概率来保证每次抽奖的公平性。文章详细描述了奖品配置、随机值生成、实现方式以及测试结果,并且使用了Java代码示例来说明具体实现。

核心思想

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

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

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

奖品名称
中奖概率
汤臣一品
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()这个方法的作用是返回这个映射中最小键(即第一个键)所对应的键值对

咱这日子也是好上了,都用上AI了(无广)

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

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号