基于 Hutool 的抽奖实现与原理

news2025/1/16 16:41:58

前言

先大概描述下 hutool 工具是如何根据权重进行抽取,后面再结合源码进行讲解。
假设有如下奖品以及对应的权重:

奖品名称权重奖品数量
谢谢参与0.760
10积分0.4550
IPhone 140.055
Mac Book Air0.011

需要注意 谢谢参与 也算是一种奖品,因为它也能被抽中。

hutool 的工具会根据 总权重 * 随机数 得到一个随机的权重,然后取第一个大于等于该 随机权重 的奖品作为抽中的结果。

说白了就是将奖品按如下分割出自己的区域,然后随机生成一个在范围内的数,看这个数落在哪一个区间上面。
在这里插入图片描述

代码实现

导入 Maven 依赖,或者自行前往 Hutool 官网下载 jar 包

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.11</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

抽奖代码实现

/**
 * 抽奖实现
 *
 * @author thai
 */
public class LuckyDraw {

    public static void main(String[] args) {
        List<Prize> prizes = List.of(
                new Prize("谢谢参与", 60, 0.7),
                new Prize("IPhone 14", 5, 0.05),
                new Prize("Mac Air M2", 1, 0.01),
                new Prize("10 积分", 50, 0.45)
        );
        List<WeightRandom.WeightObj<Prize>> weightObjs = prizes.stream()
                .map(p -> new WeightRandom.WeightObj<>(p, p.getProbability()))
                .collect(Collectors.toList());

        WeightRandom<Prize> weightRandom = RandomUtil.weightRandom(weightObjs);
        // 连续抽奖 100 次,抽中结果如下
        for (int i = 0; i < 100; i++) {
            Prize prize = weightRandom.next();
            // 抽到某个奖品小于等于 0 的直接打印谢谢参与
            if (prize.getQuantity() <= 0) {
                System.out.println("谢谢参与");
                continue;
            }
            prize.setQuantity(prize.getQuantity() - 1);
            System.out.println("恭喜您抽到了:" + prize.getName());
        }
    }

    @Data
    @AllArgsConstructor
    static class Prize {
        /**
         * 奖品名称
         */
        private String name;

        /**
         * 奖品数量
         */
        private int quantity;

        /**
         * 中奖概率
         */
        private double probability;
    }

}

执行上面的代码,打印出的结果如下:

恭喜您抽到了:10 积分
恭喜您抽到了:10 积分
恭喜您抽到了:谢谢参与
恭喜您抽到了:谢谢参与
...
恭喜您抽到了:10 积分
恭喜您抽到了:IPhone 14
恭喜您抽到了:谢谢参与

Hutool 根据权重抽取的原理分析

在构造 WeightRandom 对象时,实际会将它们添加到一个 TreeMap

public WeightRandom(Iterable<WeightObj<T>> weightObjs) {
	// 初始化 TreeMap
	this();
	if(CollUtil.isNotEmpty(weightObjs)) {
		for (WeightObj<T> weightObj : weightObjs) {
			add(weightObj);
		}
	}
}
/**
 * 增加对象权重
 *
 * @param weightObj 权重对象
 * @return this
 */
public WeightRandom<T> add(WeightObj<T> weightObj) {
	if(null != weightObj) {
		final double weight = weightObj.getWeight();
		if(weightObj.getWeight() > 0) {
			/* 
			 由于 key 为权重,这一步做的工作其实就是将所有的权重累加起来,
			 lastWeight 其实就是在这之前所有权重之和,那么后面获取总权重,
			 只需要拿 Map 最后一个 key 就可以了
			*/
			double lastWeight = (this.weightMap.size() == 0) ? 0 : this.weightMap.lastKey();
			this.weightMap.put(weight + lastWeight, weightObj.getObj());
		}
	}
	return this;
}

让我们再看看核心的 weightRandom.next() 方法

/**
 * 下一个随机对象
 *
 * @return 随机对象
 */
public T next() {
	if(MapUtil.isEmpty(this.weightMap)) {
		return null;
	}
	final Random random = RandomUtil.getRandom();
	// 总权重 * 随机数,这里的随机数范围在 [0.0, 1.0) 之间
	final double randomWeight = this.weightMap.lastKey() * random.nextDouble();
	// tailMap 表示从 Map 中截取大于等于 randomWeight 的 key 数据
	final SortedMap<Double, T> tailMap = this.weightMap.tailMap(randomWeight, false);
	// 取第一个 key
	return this.weightMap.get(tailMap.firstKey());
}

补充说明

对上述 tailMap 的一个补充说明

    public static void main(String[] args) {
        TreeMap<Integer, Integer> treeMap = new TreeMap<>();
        treeMap.put(1, 1);
        treeMap.put(2, 2);
        treeMap.put(3, 3);
        treeMap.put(4, 4);

        SortedMap<Integer, Integer> sortedMap = treeMap.tailMap(2);
        // 输出:{2=2, 3=3, 4=4}
        System.out.println(sortedMap);
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/162212.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringCloud-Netflix学习笔记04——Eureka注册中心搭建

前言 Eureka注册中心相当于Zookeeper注册中心&#xff0c;思想是类似的&#xff0c;只不过Zookeeper需要在本机上下载一个服务客户端&#xff0c;直接启动客户端即可&#xff0c;而Eureka注册中心需要我们自己动手搭建&#xff0c;不过也不难。 搭建步骤 1、新建一个Maven项目…

PySpark数据计算中常用的成员方法(算子)

目录 一.回顾 二.数据计算 map算子 演示 flatMap算子 演示 reduceByKey算子 演示 练习案例1 需求 解决步骤 完整代码 filter算子 演示 distinct算子 演示 sortBy算子 演示 练习案例2 解决步骤 完整代码 三.总结 一.回顾 1.RDD对象是什么?为什么要使用它? RDD对象称…

SegFormer学习笔记(1)安装

一、源码&#xff1a;https://github.com/sithu31296/semantic-segmentation我并没使用SegFormer的官方源码&#xff0c;那个mmcv特磨人了&#xff0c;各种奇葩配置错误。二、环境配置新建conda环境conda create -n segformer3715 python3.7.15 选用python3.7.15(纯粹的3.7.0版…

计算机原理四_内存管理

目录儿三、内存管理3.1 内存管理基础3.1.1存储器的多层结构3.1.2 进程运行基本原理进程的装入3.1.3 内存扩充3.1.4 内存的分配3.1.4.1连续分配3.1.4.2非连续分配3.1.4.2.1基本分页存储管理3.1.4.2.2基本分段存储管理3.1.4.2.3 段页式管理3.2 虚拟内存管理3.2.1 虚拟内存的概念3…

【BP靶场portswigger-客户端11】跨站点脚本XSS-10个实验(下)

前言&#xff1a; 介绍&#xff1a; 博主&#xff1a;网络安全领域狂热爱好者&#xff08;承诺在CSDN永久无偿分享文章&#xff09;。 殊荣&#xff1a;CSDN网络安全领域优质创作者&#xff0c;2022年双十一业务安全保卫战-某厂第一名&#xff0c;某厂特邀数字业务安全研究员&…

【Go基础】函数和面向接口编程

文章目录一、函数1. 函数的基本形式2. 递归函数3. 匿名函数4. 闭包5. 延迟调用defer6. 异常处理二、面向接口编程1. 接口的基本概念2. 接口的使用3. 接口的赋值4. 接口嵌入5. 空接口6. 类型断言7. 面向接口编程一、函数 1. 函数的基本形式 // 函数定义&#xff1a;a,b是形参 …

【测试】自动化测试

努力经营当下&#xff0c;直至未来明朗&#xff01; 文章目录一、自动化概述二、自动化测试的分类三、自动化测试工具&#xff1a;selenium四、一个简单的自动化用例五、Selenium常用方法1. 查找页面元素&#xff1a;2.元素的定位&#xff08;By类&#xff09;小结普通小孩也要…

Java中this的用法

一、this关键字 1.this的类型&#xff1a;哪个对象调用就是哪个对象的引用类型 二、用法总结 1.this.data; //访问属性 2.this.func(); //访问方法 3.this(); //调用本类中其他构造方法 三、解释用法 1.this.data 这种是在成员方法中使用 让我们来看看不加this会出现什…

ArcGIS基础实验操作100例--实验95平滑处理栅格数据

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 空间分析篇--实验95 平滑处理栅格数据 目录 一、实验背景 二、实验数据 三、实验步骤 &#xff08;1&…

ST算法解决BMQ问题详解(图文并茂,保证看懂)

一.RMQ问题的概念RMQ&#xff08;Range Minimum/Maximum Query&#xff09;问题&#xff0c;简单说就是求区间最值问题&#xff0c;是求区间最大值或最小值&#xff0c;即范围最值问题&#xff0c;若是简单的单次询问或者是区间长度很短的询问&#xff0c;可以用暴力的方法来实…

【web安全】——文件上传漏洞

作者名&#xff1a;白昼安全主页面链接&#xff1a; 主页传送门创作初心&#xff1a; 舞台再大&#xff0c;你不上台&#xff0c;永远是观众&#xff0c;没人会关心你努不努力&#xff0c;摔的痛不痛&#xff0c;他们只会看你最后站在什么位置&#xff0c;然后羡慕或鄙夷座右铭…

【寒假第2天】LeetCode刷题

&#x1f308;一、选择题 &#x1f47f;第1题&#xff1a; 下面给出的四种排序法中( )排序法是不稳定性排序法A.插入排序 B.冒泡排序 C.归并排序 D.堆&#xff0c;希尔排序&#xff0c;快速排序 答案&#xff1a;D 为啥堆排序是不稳定的&am…

SCA 工具:开源安全威胁一手掌控

1、什么是 SCA SCA&#xff08;Software Composition Analysis&#xff09;软件成分分析&#xff0c;通俗的理解就是通过分析软件包含的一些信息和特征来实现对该软件的识别、管理、追踪的技术。我们知道在当今软件开发中&#xff0c;引入开源软件(注 1)到你的项目中&#xff…

线性DP-----(从某点走到某点求最值问题)

线性DP 线性dp问题是dp问题中比较简单的问题,通常一个状态转移方程就可以搞定,线性dp通常求最大值,最小值问题,下面介绍线性dp中从某点走到某点最值问题。 第一类问题(走一遍) 该类问题只走一遍,动态规划中用到的数组f(i,j)含义就是到达(i,j)点得到的最优解 例题1—数字三角形 …

分享88个JavaScript源码,总有一款适合您

JavaScript源码 分享88个JavaScript源码&#xff0c;总有一款适合您 JavaScript源码下载链接&#xff1a;https://pan.baidu.com/s/1guiYWOPKdP1zNW7T8P0caQ?pwd6666 提取码&#xff1a;6666 采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 下面是文件的名字&#xf…

jinja2 循环计数内置变量loop

变量内容loop.index循环迭代计数&#xff08;从1开始&#xff09;loop.index0循环迭代计数&#xff08;从0开始&#xff09;loop.revindex循环迭代倒序计数&#xff08;从len开始&#xff0c;到1结束&#xff09;loop.revindex0循环迭代倒序计数&#xff08;从len&#xff0d;1…

【正点原子FPGA连载】 第十八章双目OV5640摄像头HDMI显示实验 摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

1&#xff09;实验平台&#xff1a;正点原子MPSoC开发板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id692450874670 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第十八章双目OV5…

jsp题库管理系统Myeclipse开发sqlserver数据库web结构java编程计算机网页项目

一、源码特点 jsp 题库管理系统 是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开 发&#xff0c;数据库为sqlserver&#xff0c;使…

Kafka集群安装

Apache kafka是由Apache软件基金会开发的一个开源流处理平台&#xff0c;由Scala和Java编写。Kafka是一种高吞吐量的分布式发布订阅消息系统&#xff0c;是消息中间件的一种&#xff0c;用于构建实时数据管道和流应用程序。Kafka官网&#xff1a;http://kafka.apache.org/安装环…

1-连续系统PID的Simulink仿真

以二阶线性传递函数。为被控对象&#xff0c;进行模拟PID控制。在信号发生器中选择正弦信号&#xff0c;仿真时取&#xff0c;&#xff0c;&#xff0c;输入指令为&#xff0c;其中A1.0,F0.20Hz。采用ODE45迭代方法&#xff0c;仿真时间为10s。PID控制器由Simulink下的工具箱提…