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

简介

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

什么是正态分布(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;
        }
    }
}
相关推荐
王码码20355 小时前
Go语言的测试:从单元测试到集成测试
后端·golang·go·接口
王码码20355 小时前
Go语言中的测试:从单元测试到集成测试
后端·golang·go·接口
嵌入式×边缘AI:打怪升级日志6 小时前
使用JsonRPC实现前后台
前端·后端
小码哥_常7 小时前
从0到1:Spring Boot 中WebSocket实战揭秘,开启实时通信新时代
后端
lolo大魔王7 小时前
Go语言的异常处理
开发语言·后端·golang
IT_陈寒9 小时前
Python多进程共享变量那个坑,我差点没爬出来
前端·人工智能·后端
码事漫谈9 小时前
2026软考高级·系统架构设计师备考指南
后端
AI茶水间管理员10 小时前
如何让LLM稳定输出 JSON 格式结果?
前端·人工智能·后端
其实是白羊11 小时前
我用 Vibe Coding 搓了一个 IDEA 插件,复制URI 再也不用手动拼了
后端·intellij idea
用户83562907805111 小时前
Python 操作 Word 文档节与页面设置
后端·python