公司最近策划一个红包活动,指定金额指定上下限后随机分发成若干个红包,以抽奖机制抽给员工。拿到这活,首先想到的当然是度娘哈,偷师学艺之后,初步进行代码实现,下面讲述下原理。

主要分两步:红包随机分割和随机抽奖

  • 红包随机分割  依据红包个数,分割出可供随机分配的剩余金额,计算出剩余金额每个红包可以分配的最小金额、剩余金额每个红包可以分配的最大金额,获取每个红包可分配的随机值,计算出每个红包的金额:红包下限+可分配随机值,最后打乱红包集   复杂度 o(n)
  • 随机抽奖    采用经典的Alias method/别名采样方法  复杂度为 o(1)

下面来看代码:红包分割时,得保证不会出现差异很大红包下限和上限,限值比例(0.25/1.25)可以微调。红包分配完之后,入库存储以供随机抽奖

  /*** * 金额随机分配,上下限可以不设置* * @param cashAmount*            金额(单位:分)* @param allocationNumber*            分配份数* @param lowerLimit*            下限(单位:分)、没有下限限制时传入0* @param upperLimit*            上限(单位:分)、没有上限限制时传入0* @throws BusinessErrorException* @author syuson* @return ArrayList<Integer> 金额集合,单位分*/public static ArrayList<Integer> CashAllocation(Long cashAmount, Long allocationNumber, Long lowerLimit,Long upperLimit) throws BusinessErrorException {// 保证不会出现差异很大的红包下限if (0 == lowerLimit) {lowerLimit = (long) ((cashAmount / allocationNumber) * 0.25);}// 保证不会出现差异很大的红包上限if (0 == upperLimit) {upperLimit = (long) ((cashAmount / allocationNumber) * 1.25);}// 异常检测exceptionValidate(cashAmount, allocationNumber, lowerLimit, upperLimit);// 可供分配Long remainCashAmount = cashAmount - (lowerLimit * allocationNumber);ArrayList<Integer> cashList = new ArrayList<Integer>(allocationNumber.intValue());// 剩余金额每个红包可以分配的最小金额Long devideAmount = remainCashAmount / allocationNumber;int randomMinAmount = devideAmount.intValue();// 剩余金额每个红包可以分配的最大金额Long subtractAmount = upperLimit - lowerLimit;int randomMaxAmount = subtractAmount.intValue();for (int i = 1; i <= allocationNumber; i++) {Random random = new Random();int randomAmount = 0;// 获取每个红包可分配的随机值if (randomMaxAmount < randomMinAmount || remainCashAmount == 0) {randomAmount = remainCashAmount.intValue();} else {randomAmount = random.nextInt(randomMaxAmount) % (randomMaxAmount - randomMinAmount + 1)+ randomMinAmount;}// 计算出每个红包的金额,红包下限+可分配随机值cashList.add(lowerLimit.intValue() + randomAmount);remainCashAmount = remainCashAmount - randomAmount;if (remainCashAmount <= randomMaxAmount) {randomMaxAmount = remainCashAmount.intValue();}}// 打乱红包Collections.shuffle(cashList);return cashList;}

异常检测机制

   /*** * 异常检测* * @param cashAmount*            金额* @param allocationNumber*            分配份数* @param lowerLimit*            下限* @param upperLimit*            上限* @throws BusinessErrorException* @author syuson*/private static void exceptionValidate(Long cashAmount, Long allocationNumber, Long lowerLimit, Long upperLimit)throws BusinessErrorException {if (0 >= cashAmount) {throw new BusinessErrorException("总金额异常");}Long remainCashAmount = cashAmount - lowerLimit * allocationNumber;if (0 > remainCashAmount) {throw new BusinessErrorException("分配份数、下限、金额总额异常");}if (0 > lowerLimit || 0 >= upperLimit || lowerLimit > upperLimit) {throw new BusinessErrorException("上限、下限金额异常");}Long maxAllocationCash = upperLimit * allocationNumber;if (cashAmount > maxAllocationCash) {throw new BusinessErrorException("分配份数、上限、金额总额异常");}}

测试:

  public static void main(String[] args) throws BusinessErrorException {// 单个红包下限Long minAmount = 50L;// 单个红包上限Long maxAmount = 400L;// 需要的红包总数量Long total = 10000L;// 可用的总金额(需要介于minAmount*total ,maxAmount*total)Long totalAmount = 2000000L;Long startime = (new Date()).getTime();ArrayList<Integer> cashList = CashAllocation(totalAmount, total, minAmount, maxAmount);Long endtime = (new Date()).getTime();Iterator<Integer> cashListIterator = cashList.iterator();//int i = 0;BigDecimal hundred = new BigDecimal(100);BigDecimal sum = new BigDecimal(0);while(cashListIterator.hasNext()){BigDecimal cash = new BigDecimal(cashListIterator.next()).divide(hundred, 2, RoundingMode.HALF_DOWN);//i++;sum = sum.add(cash);//System.out.println(String.format("第%s个红包金额:%s元",i,cash));}System.out.println("红包总额(元):" + sum);System.out.println("开始时间(豪秒):" + startime);System.out.println("结束时间(豪秒):" + endtime);System.out.println("分配个数:"+cashList.size()+",耗费时间(豪秒):" + (endtime - startime));}
红包总额(元):20000.00
开始时间(豪秒):1520909221310
结束时间(豪秒):1520909221318
分配个数:10000,耗费时间(豪秒):8

Alias method 即别名采样法,有兴趣的可以参考博文[对点我],大致算法:

  1. 将整个概率分布拉平成为一个1*N的长方形即为Alias Table。储存两个数组,一个数组里面存着第ii列对应的事件ii矩形站的面积百分比即概率,另一个数组里面储存着第ii列不是事件ii的另外一个事件的标号
  2. 产生两个随机数,第一个产生1~N 之间的整数i,决定落在哪一列。扔第二次骰子,0~1之间的任意数,判断其与Prab[i]大小,如果小于Prab[i],则采样i,如果大于Prab[i],则采样Alias[i]
 /* The random number generator used to sample from the distribution. */private final Random random;/* The probability and alias tables. */private final int[] alias;private final double[] probability;/*** Constructs a new AliasMethod to sample from a discrete distribution and* hand back outcomes based on the probability distribution.* <p/>* Given as input a list of probabilities corresponding to outcomes 0, 1,* ..., n - 1, this constructor creates the probability and alias tables* needed to efficiently sample from this distribution.** @param probabilities*            The list of probabilities.*/public AliasMethod(List<Double> probabilities) {this(probabilities, new Random());}/*** Constructs a new AliasMethod to sample from a discrete distribution and* hand back outcomes based on the probability distribution.* <p/>* Given as input a list of probabilities corresponding to outcomes 0, 1,* ..., n - 1, along with the random number generator that should be used as* the underlying generator, this constructor creates the probability and* alias tables needed to efficiently sample from this distribution.** @param probabilities*            The list of probabilities.* @param random*            The random number generator*/public AliasMethod(List<Double> probabilities, Random random) {/* Begin by doing basic structural checks on the inputs. */if (probabilities == null || random == null)throw new NullPointerException();if (probabilities.size() == 0)throw new IllegalArgumentException("Probability vector must be nonempty.");/* Allocate space for the probability and alias tables. */probability = new double[probabilities.size()];alias = new int[probabilities.size()];/* Store the underlying generator. */this.random = random;/* Compute the average probability and cache it for later use. */final double average = 1.0 / probabilities.size();/** Make a copy of the probabilities list, since we will be making* changes to it.*/probabilities = new ArrayList<Double>(probabilities);/* Create two stacks to act as worklists as we populate the tables. */Deque<Integer> small = new ArrayDeque<Integer>();Deque<Integer> large = new ArrayDeque<Integer>();/* Populate the stacks with the input probabilities. */for (int i = 0; i < probabilities.size(); ++i) {/** If the probability is below the average probability, then we add* it to the small list; otherwise we add it to the large list.*/if (probabilities.get(i) >= average)large.add(i);elsesmall.add(i);}/** As a note: in the mathematical specification of the algorithm, we* will always exhaust the small list before the big list. However, due* to floating point inaccuracies, this is not necessarily true.* Consequently, this inner loop (which tries to pair small and large* elements) will have to check that both lists aren't empty.*/while (!small.isEmpty() && !large.isEmpty()) {/* Get the index of the small and the large probabilities. */int less = small.removeLast();int more = large.removeLast();/** These probabilities have not yet been scaled up to be such that* 1/n is given weight 1.0. We do this here instead.*/probability[less] = probabilities.get(less) * probabilities.size();alias[less] = more;/** Decrease the probability of the larger one by the appropriate* amount.*/probabilities.set(more, (probabilities.get(more) + probabilities.get(less)) - average);/** If the new probability is less than the average, add it into the* small list; otherwise add it to the large list.*/if (probabilities.get(more) >= 1.0 / probabilities.size())large.add(more);elsesmall.add(more);}/** At this point, everything is in one list, which means that the* remaining probabilities should all be 1/n. Based on this, set them* appropriately. Due to numerical issues, we can't be sure which stack* will hold the entries, so we empty both.*/while (!small.isEmpty())probability[small.removeLast()] = 1.0;while (!large.isEmpty())probability[large.removeLast()] = 1.0;}/*** Samples a value from the underlying distribution.** @return A random value sampled from the underlying distribution.*/public int next() {/* Generate a fair die roll to determine which column to inspect. */int column = random.nextInt(probability.length);/* Generate a biased coin toss to determine which option to pick. */boolean coinToss = random.nextDouble() < probability[column];/* Based on the outcome, return either the column or its alias. *//** Log.i("1234","column="+column); Log.i("1234","coinToss="+coinToss);* Log.i("1234","alias[column]="+coinToss);*/return coinToss ? column : alias[column];}/** 匹配、选中 */private final static String RANDOM_MATCHED = "RandomMatched";/** 未匹配、未选上 */private final static String NO_RANDOM_MATCHED = "NoRandomMatched";/*** 随机匹配抽奖* * @param probability*            匹配抽奖中奖概率* @return true:中奖/false:未中奖*/public static Boolean SystemRandomMatche(Double probability) {if (1 < probability)probability = 0.1;TreeMap<String, Double> map = new TreeMap<String, Double>();map.put(RANDOM_MATCHED, probability);map.put(NO_RANDOM_MATCHED, 1.0 - probability);List<Double> list = new ArrayList<Double>(map.values());List<String> gifts = new ArrayList<String>(map.keySet());AliasMethod method = new AliasMethod(list);String key = gifts.get(method.next());if (RANDOM_MATCHED.equalsIgnoreCase(key.trim()))return true;return false;}

来看看测试:

 public static void main(String[] args) {boolean showFlag = true;if(!showFlag){Double probability = 0.35;int exeNum = 10;for (int j = 0; j < exeNum; j++) {System.out.println(String.format("抽奖概率为:%s,抽奖结果为:%s", probability, SystemRandomMatche(probability)?"中奖":"谢谢参与"));}}if (showFlag) {TreeMap<String, Double> map = new TreeMap<String, Double>();map.put("1级晶石", 0.25);map.put("10药水", 0.25);map.put("5钱袋", 0.20);map.put("1饭盒", 0.1);map.put("2饭盒", 0.1);map.put("1碎片", 0.096);map.put("30碎片", 0.002);map.put("6666钻", 0.002);List<Double> list = new ArrayList<Double>(map.values());List<String> gifts = new ArrayList<String>(map.keySet());AliasMethod method = new AliasMethod(list);Map<String, AtomicInteger> resultMap = new HashMap<String, AtomicInteger>();for (String in : map.keySet()) {resultMap.put(in, new AtomicInteger());}int exeNum = 15;for (int i = 0; i < exeNum; i++) {int index = method.next();String key = gifts.get(index);if (!resultMap.containsKey(key)) {resultMap.put(key, new AtomicInteger());}resultMap.get(key).incrementAndGet();}System.out.println("抽奖次数:"+ exeNum);for (String key : resultMap.keySet()) {System.out.println(String.format("[%s,设定概率:%s],实际抽中次数:%s",key,map.get(key),resultMap.get(key)));}}}
抽奖次数:15
[30碎片,设定概率:0.002],实际抽中次数:0
[10药水,设定概率:0.25],实际抽中次数:2
[2饭盒,设定概率:0.1],实际抽中次数:3
[1碎片,设定概率:0.096],实际抽中次数:0
[1饭盒,设定概率:0.1],实际抽中次数:1
[6666钻,设定概率:0.002],实际抽中次数:0
[5钱袋,设定概率:0.2],实际抽中次数:5
[1级晶石,设定概率:0.25],实际抽中次数:4

抽奖机制:给定概率值(随机生成一个),通过Alias method获知是否抽中,抽中之后去之前存储红包的库中,随机抽一个红包,如果没中,很遗憾~

    private Double generateProbability(int liseSize) {BigDecimal probability = new BigDecimal(0.19);if (3 < liseSize) {return probability.doubleValue();}return (BigDecimal.ONE.subtract(probability.multiply(new BigDecimal(liseSize)))).setScale(2, RoundingMode.HALF_UP).subtract(probability).doubleValue();}
      // 领用过该红包并且尚未使用的用户需进行随机匹配,次数越多概率越低int listSize = bonus2UserListSize(eventId, userId);if (listSize >= 1) {double probability = generateProbability(listSize);String msg = String.format("该用户%s(%s)已领用红包,此次随机匹配概率为:%s", user.getNickName(), user.getAuthUid(),probability);logger.debug(msg);if (!AliasMethod.SystemRandomMatche(probability)) {return OperationResult.buildFailureResult(msg);}}

有啥问题希望大家留言,一起学习~

转载于:https://my.oschina.net/syuson/blog/1633786

红包分配:指定金额指定上下限后随机分发成若干个红包,随机抽相关推荐

  1. 编写一个函数,模拟微信发红包的红包分配过程。函数有两个参数:一个参数表示红包总金额,默认值为100,另一个参数表示红包数量,默认为10。程序输入:红包总金额和红包数量;程序输出:每个红包的金额。要求:

    题目 ‬‬编写一个函数,模拟微信发红包的红包分配过程.函数有两个参数:一个参数表示红包总金额,默认值为100,另一个参数表示红包数量,默认为10.程序输入:红包总金额和红包数量:程序输出:每个红包的金 ...

  2. php 红包算法教程,php仿微信红包分配算法的实现方法

    php仿微信红包分配算法的实现方法 本文实例讲述了php仿微信红包分配算法的实现方法.分享给大家供大家参考,具体如下: /** * 红包分配:把一定金额随机分配给指定人数 * * @param int ...

  3. 红包指定分配金额php,php仿微信红包分配算法的实现方法_PHP

    本文实例讲述了php仿微信红包分配算法的实现方法.分享给大家供大家参考,具体如下: /** * 红包分配:把一定金额随机分配给指定人数 * * @param int $money 用于分配的金额 * ...

  4. java红包金额随机数算法_实时随机数算法(微信红包分配算法)

    微信红包算法在知乎上面有个专题讨论,其实红包的发放的随机算法,有两种作法:java 一.预生产: 算法 无外乎是在发红包的时候,随机去把金额生成到某个容器当中,而后要用的时候,一个一个的POP:微信 ...

  5. DOM中setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式。

    setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式. <html> <head> <script type="text/javascript ...

  6. 数据结构: 试用判定树的方法给出在中序线索化二叉树上: (1) 如何搜索指定结点的在中序下的后继。 (2) 如何搜索指定结点的在前序下的后继。(3) 如何搜索指定结点的在后序下的后继。

    题目 试用判定树的方法给出在中序线索化二叉树上: (1) 如何搜索指定结点的在中序下的后继. (2) 如何搜索指定结点的在前序下的后继. (3) 如何搜索指定结点的在后序下的后继. 分析 这是殷人昆& ...

  7. 定义一个类:实现功能可以返回随机的10个数字,随机的10个字母, 随机的10个字母和数字的组合;字母和数字的范围可以指定,类似(1~100)(A~z)...

    #习题2:定义一个类:实现功能可以返回随机的10个数字,随机的10个字母, #随机的10个字母和数字的组合:字母和数字的范围可以指定class RandomString():#随机数选择的范围作为参数 ...

  8. 算法学习笔记:对指定金额计算最少钞票数

    算法学习笔记:对指定金额计算最少钞票数 一.引出问题 财务人员给员工发工资时经常遇到这样一个问题,即根据每个人的工资额(以元作为单位)计算出各种面值的钞票的张数,且要求总张数最少.例如,某职工工资为3 ...

  9. 提供源码:java获取节假日、工作日,存入数据库,查找指定日期前一天,后一天。

    提供源码:java获取节假日.工作日,存入数据库,查找指定日期前一天,后一天. 码云地址: https://gitee.com/guyuanman/holiday 业务场景: 好多公司的业务在处理的时 ...

最新文章

  1. Java枚举原来还能这么用
  2. msoffice二级各题题型及其分值
  3. 外贸网络推广浅析新建网站该如何更快速进入搜索结果首页?
  4. [Android] 使用Include布局+Fragment滑动切换屏幕
  5. TCP服务器端和客户端建立连接 - 服务器端的回调处理
  6. P1422 小玉家的电费--2022.03.15
  7. mysql 安装服务 w_MySQL的安装与配置
  8. SpringBoot使用Mybatis-PageHelper
  9. 2020.07.08_Multi-passage BERT: A Globally Normalized BERT Model for Open-domain Question Answering
  10. linux安装nfs服务器
  11. python @staticmethod方法
  12. 汇川plc c语言,汇川plc可编程控制器模块种类
  13. centerOS 7.6FTP安装与配置
  14. *基于类平衡自我训练的无监督域自适应用于语义分割
  15. edge搁置标签页_如何自定义Microsoft Edge的新标签页
  16. Ansible hosts文件写法
  17. 回看共识层进化规律,“POS+”也许是公链后期发展的出路
  18. 第三次作业:卷积神经网络基础
  19. 如何应用卫星图像到Auto CAD
  20. 加速想象力 AR/VR 训练营(无锡站)签约挂牌仪式成功举行

热门文章

  1. 低频理疗按摩仪8种常用基本波形
  2. 对话知名视觉艺术设计师走尺:只要用心 人人是插画师
  3. “华为号”,决定穿越计算光年
  4. C#射击类小游戏简单思路及代码
  5. php 有下划线的方法,编码风格 - PHP类方法中领先的下划线有什么用?
  6. Web_制作页面开场动画并解决自动播放问题
  7. 小猿圈IT自学分享-自学编程需要克服的困难
  8. 获取文件夹下的文件,包含子文件夹并复制文件
  9. VBA基础函数:取数组最大下标——UBound函数
  10. Introduction to Graph Neural Network(图神经网络概论)翻译:目录总览