基于正态分布的红包生成算法

简介

如果想生成的红包大小相对均衡,可以同时控制最大最小和总额,那么不妨试试我这个方法。 在此之前我们先简单介绍一下正态分布。

什么是正态分布(normal distribution)

复习一下高中知识,正态分布又叫做常态分布、高斯分布(Gaussian distribution)。

其中有两个有两个参数: mu为学期望,可以看作最高概率的点。 sigma^2为方差(实数),可以看作分布的均匀程度。

正态分布的应用

现实中很多现象都符合正态分布,十分神奇,比如下面这个图。 我们可以利用正态分布对很多分布情况建模。所以我们如果想创建随机分布的红包也可以用使用随机分配红包。

java中的正态分布

JDK中Random类中的nextGaussian()方法,可以产生服从标准正态分布的随机数。即 X~N(0,1);

如果我们想产生自定义的正态分布呢 X~N(μ,b) b=σ^2;

方差 * 正态分布数据 + 正态分布中心位置 产生N(a,b)的数:

java 复制代码
Math.sqrt(b)*random.nextGaussian()+a;

即均值为a,方差为b的随机数

代码实现

整体思想: 其实就是将数据分为两部分,先用最小值min铺平,上面使用正态分布分配数值。那么如何使用正态分布分配呢?首先可以先用上面提到的公式产生n份服从正态分布的0~1之间的随机数,然后对这些随机数做一个概率计算,即求出每份的占比,使用占比与剩余总数相乘的到红包大小。当然,如果有最大值限制,这个过程需要循环进行,最终会把峰值切掉,补充到后面的红包上。

java 复制代码
import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Random;
import java.util.stream.IntStream;

/**
 * @Description 红包生成器
 * @Date 2021/1/22
 * @Author yuvenhol
 */
@Slf4j
public class RedPacketGenerator {

    public static final Random random = new Random();

    public static BigDecimal[] genPackets(Param param) {
        ExceptionUtil.check(!check(param), "红包参数不正常");
        BigDecimal leftMoney = param.total;//可分配的金额
        BigDecimal min = param.min;// 最小
        BigDecimal max = param.max;// 最大
        Integer count = param.count.intValue();// 份数
        BigDecimal[] result = new BigDecimal[count];
        //数组所有元素都赋最小值 铺底
        IntStream.range(0, count).forEach(x -> result[x] = min);
        leftMoney = leftMoney.subtract(min.multiply(param.count));
        //定义正态分布的随机数组
        BigDecimal[] normalNumbers = new BigDecimal[count];
        //给数组赋值
        IntStream.range(0, count).forEach(x -> normalNumbers[x] = NormalDistribution(param.variance));
        //统计随机数总合
        BigDecimal countNormalNumbers = Arrays.stream(normalNumbers).reduce((x1, x2) -> x1.add(x2)).get();
        //每份金额, 这里提高精度减小误差
        BigDecimal perAmount = leftMoney.divide(countNormalNumbers, 10, BigDecimal.ROUND_HALF_UP);
        //此时 perAmount * countNormalNumbers = leftMoney,
        int i = -1;
        while (leftMoney.compareTo(BigDecimal.ZERO) > 0) {
            i = ++i % count;
            // 取随机安全值
            BigDecimal addAmount = perAmount.multiply(normalNumbers[i]).setScale(2, BigDecimal.ROUND_HALF_UP);
            //超越上限
            if (addAmount.add(result[i]).compareTo(max) > 0) {
                continue;
            }
            //防止剩余钱数减超
            if (leftMoney.subtract(addAmount).compareTo(BigDecimal.ZERO) <= 0) {
                addAmount = leftMoney;
                leftMoney = BigDecimal.ZERO;
            } else {
                leftMoney = leftMoney.subtract(addAmount);
            }
            result[i] = result[i].add(addAmount);
        }
        return result;
    }

    public static boolean check(Param param) {
        if (param == null) {
            log.error("参数为空");
            return false;
        }
        if (param.max.compareTo(param.min) < 0) {
            log.error("最大最小值不匹配");
            return false;
        }
        if (param.max.multiply(param.count).compareTo(param.total) < 0) {
            log.error("最大值不能超过总值");
            return false;
        }
        return true;
    }

    public static boolean check(RedPacket redPacket) {
        return check(new RedPacketGenerator.Param(redPacket.getRealTotalMoney(), redPacket.getRealCount(), redPacket.getMax(), redPacket.getMin()));
    }


    /**
     * 获取 0-1正态分布的随机值
     *
     * @param v 方差
     * @return 随机值
     */
    public static BigDecimal NormalDistribution(double v) {
        double r;
        int varianceRange = 3; //这里正态分布N中三倍标准差 (99.7%)。
        do {
            r = Math.sqrt(v) * random.nextGaussian();
        }
        while (r < -varianceRange || r > varianceRange);//过滤掉超过规定范围的特殊值
        r = (r / varianceRange + 1) / 2;//整理成 0-1之间的数
        return new BigDecimal(r);
    }

    public static class Param {
        BigDecimal total;// 总金额
        BigDecimal min;// 最小
        BigDecimal max;// 最大
        BigDecimal count;// 份数
        double variance;//方差


        public Param(BigDecimal totalMoney, int count, BigDecimal max, BigDecimal min) {
            this(totalMoney, count, 10, max, min);
        }

        public Param(BigDecimal totalMoney, int count, double variance, BigDecimal max, BigDecimal min) {
            this.total = totalMoney;
            this.min = min;
            this.max = max;
            this.count = BigDecimal.valueOf(count);
            this.variance = variance;
        }
    }
}
相关推荐
红尘散仙4 小时前
我把终端小说阅读器接上了 AI Agent:TRNovel 现在能用 skill 生成书源了
人工智能·后端·rust
卷毛的技术笔记6 小时前
告别硬编码!Spring AI Alibaba 实现 AI Agent 智能工具调用(Tool Calling)
java·人工智能·后端·python·spring·ai编程
会编程的土豆6 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
喵个咪6 小时前
GoWind Toolkit Go后端代码生成 完整全流程实战
后端·go·orm
basketball6167 小时前
Go 语言从入门到进阶:4. 数组和MAP使用方法总结
开发语言·后端·golang
qq_2518364577 小时前
SpringBoot+Vue 共享电池柜管理系统 完整实现 前后端分离项目实战 完整代码
vue.js·spring boot·后端
zhangxingchao7 小时前
AI 大模型核心六:量化、Workflow 与 Agent、多轮 RAG
前端·人工智能·后端
IT_陈寒8 小时前
Vite打包时遇到的坑,原来问题出在这里
前端·人工智能·后端
ayqy贾杰9 小时前
基层管理的三板斧,在AI时代行不通了
前端·后端·团队管理
Apifox9 小时前
Apifox 5 月更新|Postman 导入优化、Runner 支持非 root 运行、请求代码自动带鉴权
前端·后端·安全