文章目录

  • 1.前言
  • 2.参考微信群红包算法
  • 3.一个可用的随机算法
  • 参考文献

1.前言

因疫情影响,部门 2021 年会以线上直播的形式进行,通过微信小程序展开。为活跃年会氛围,年会直播间会有抢红包环节。因产品要求,红包金额要随机生成,所以这里涉及到指定红包总额、数量和最大最小值情况下如何生成红包金额。

可以看出,红包随机金额生成的输入是一个四元组 <sum, num, min, max>,其中 sum 是红包总额,num 是红包数量,min 和 max 分别是红包最小最大值。所以这里可以抽象成如下算法:

input: <sum, num, min, max>
output: 随机红包金额数组

因为法币都有最小单位,比如人民币是分,所以上面输入四元组均可视为整数。

2.参考微信群红包算法

本质上,这和微信群红包没什么区别,发出一个固定总金额的红包,指定红包数量,那么随机分配红包金额时需要满足哪些规则?
(1)所有人抢到金额之和等于红包总金额,不能超过,也不能少于;
(2)抢到的红包金额至少是一分钱;
(3)要保证抢到红包的人获取到的红包金额是随机的。

实际上,微信群红包的 min 是 1 分钱,max 是剩余红包金额均值的两倍,为什么是这两个值,因为这么做会保证随机值的期望值等于均值,来保证不会因为抢红包的先后顺序而造成不公平。这两个值是算法内设的,不提供给用户指定。另外总金额 sum 和数量 num 是由用户指定的。

为什么微信群红包要搞一个最大上限,因为如果不设置一个最大上限,会出现一种不公平的现象。就是越在前边领取红包的同学,其可随机范围越大,获得大额红包的几率也越高。一旦前边的同学随机到一个较大的金额,后边的同学可以随机的范围就逐步收窄,抢红包就变成了一个拼手速的游戏了。

实际上,微信群红包采用的是二倍均值法,也就是每次随机上限为剩余红包金额均值的两倍

微信群红包金额分配算法是这样的:

每次抢红包直接随机,随机的范围是[1, 剩余红包金额均值的两倍],单位分

这个公式,保证了每次随机金额的平均值是相等的,不会因为抢红包的先后顺序而造成不公平。

实际上微信群红包的算法虽然公平,但是有个缺陷,不过这个微信产品同学可以接受,只是对于用户来说体验并不是那么友好,因为有时发个群红包会出现下面这种最后两个之一红包金额非常大的情况。

出现这种情况的原因是,上面的随机上限 max 为剩余红包金额均值的两倍,如果剩余红包金额均值越来越大,那么越是后抢期望越大,就会出现上面这种情况。当然,出现这种情况的概率非常小,需要除了最后两次之前的所有结果都小于剩余均值,即小金额。

这说明了一个什么问题呢?红包金额随机分配算法不是一个标准算法,而是产品逻辑。 如果你是产品同学,你完全可以搞一个你想要的随机分配算法,比如随机范围严格在 [min, max] 之间,或者像微信群红包那样,每次抢红包时,max 是动态变化的。

这里说下大家最关心的问题,就是如何才能抢到大红包。通过上面的介绍,结论就是除了最后两个红包金额之一有可能很大,因为每次都是在 [0.01 - 剩余均值*2] 之间随机,越往后剩余均值可能越大,导致最后两位抢时,期望最大。如果红包数量充足,那么最后两位抢才有可能获得大红包。但绝大部分情况是僧多粥少,需要拼手速才能抢到红包,这种情况下,你不能保证你是最后两位抢到红包的人。

3.一个可用的随机算法

此次年会产品同学开始跟我说需要像微信群红包那样的随机分配红包金额,但是仔细研究了微信群红包的算法,才发现产品同学想要的效果和微信群红包并不同,她想要的是红包金额严格随机范围在 [min, max]。

在实现时要满足如下几个条件:
(1)所有人抢到金额之和等于红包总金额,不能超过,也不能少于;
(2)抢到的红包金额在 [min, max] 之间;
(3)要保证抢到红包的人获取到的红包金额是随机的。

下面给一个可行的随机分配算法。

// min 最小金额分 max 最大金额分 num 红包数量 sum 红包总额分
input:<min, max, num, sum>// 参数合法性校验
step 1: min*num <= sum <= max*num
step 2: 将 num 个在 min 填入数组
step 3: 循环随机一个范围为 [0, max - min] 数加到最小值数组中。如果随机数大于剩余金额,则取剩余金额作为随机数;如果累加值大于最大值,则取最大值与原值差值作为随机数。如果剩余金额为 0 结束循环
step 4: 如果均值靠近 min 或 max,第三步分别会出现很多 min 或者 max,看起来不够随机。这里需要经过一轮或多轮遍历,将 (min, max) 之间的数减掉部分给到 min 或者从 max 获得部分
step 5: 打乱数组顺序

注意,在第四步消除最小值或最大值,是控制在一定比例还是完全消除,也是一个产品逻辑,需要由产品同学来定。下面的实现示例,只进行一轮循环,可能会存在少量最小值或最大值。

下面以 NodeJS 为例,给出实现。

// brief: 获取随机整数 [min, max]
function random(min, max) {const range = max - min;const rand = Math.round(Math.random() * range);return min + rand;
}// brief: 消除最小值和最大值
function smooth(min, max, arr) {for (let i = 0; i < arr.length; i++) {if (!(min < arr[i] && arr[i] < max)) {continue}for (let j = 0; j < arr.length; j++) {// 消除最小值if (arr[j] === min) {let rm = Math.floor((arr[i] - min)/10)arr[i] -= rmarr[j] += rmbreak}// 消除最大值if (arr[j] === max) {let rm = Math.floor((max - arr[i])/10)arr[i] += rmarr[j] -= rmbreak}}}
}// brief: 打乱数组顺序
function shuffle(arr) {arr.sort(() => Math.random() - 0.5);
}// brief: 生成随机整数数组
function randnum(min, max, num, sum) {// step 1 检查参数if (min <= 0 || max <= 0 || num <= 0 || sum <= 0) {return [];}if (!(min * num <= sum && sum <= max * num)) {return [];}// step 2 将 num 个在 min 填入数组var arr = new Array(num).fill(min);// step 3 循环随机生成[0, max-min]加到最小值数组let leftTotal = parseInt(sum - min*num);LABEL:while(true) {for (let i = 0; i < num; i++) {let rand = random(0, parseInt(max-min));// 如果随机数大于剩余金额,则取剩余金额作为随机数if (rand > leftTotal) {rand = leftTotal;}// 如果累加值大于最大值,则取最大值与原值差值作为随机数if (arr[i] + rand > max) {rand = max - arr[i];}arr[i] += rand;leftTotal -= rand;if (leftTotal === 0) {break LABEL;}}}// step 4 消除大部分最小值和最大值smooth(min, max, arr)// step 5 打乱数组顺序shuffle(arr)return arr;
}

上面的代码可以在 Online NodeJS IDE 执行。

下面采用两组入参,均值分别靠近最小值和最大值来观察多次运行后的输出结果。

第一组入参,最小金额 5 元,最大金额 50 元,数量 10 个,总金额 100 元。均值 10 靠近最小值。

// 单位为分
console.log(randnum(500, 5000, 10, 10000))// 第一轮结果
[ 516, 3317, 646, 515,  677, 636, 1861,  501, 518, 813 ]// 第二轮结果
[ 724,  502,  500, 726, 2761, 2740, 500,  502,  522, 523 ]// 第三轮结果
[ 500, 500, 1009, 899, 504, 4492, 500, 505,  551, 540 ]

第二组入参,均值靠近最大值,最小金额 5 元,最大金额 50 元,数量 10 个,总金额 450 元。均值 45 靠近最大值。

// 单位为分
console.log(randnum(500, 5000, 10, 40000))// 第一轮结果
[ 4832, 4999, 4953, 4990, 3515, 3929, 4835, 4572, 3482, 4893 ]// 第二轮结果
[ 4868, 5000, 4901, 5000, 4733, 5000, 4106, 3804, 2588, 5000 ]// 第三轮结果
[ 4999, 3493, 3795, 5000, 3210, 4849, 4867, 4985, 5000, 4802 ]

从上面的实验结果可以看出,相同入参多次运行结果是不同的。如果均值靠近最小值或者最大值,结果可能分别会出现多个最小值和最大值,这个可以通过多次执行 smooth 函数来完全消除。


参考文献

[1] 漫画:如何实现抢红包算法?
[2] 微信拼手气红包背后的算法逻辑

红包随机算法微信群红包随机算法相关推荐

  1. (转)红包随机算法微信群红包随机算法

    通过这篇文章算是把微信群红包的算法弄明白了 1.前言 因疫情影响,部门 2021 年会以线上直播的形式进行,通过微信小程序展开.为活跃年会氛围,年会直播间会有抢红包环节.因产品要求,红包金额要随机生成 ...

  2. 微信群红包模拟器-怎样抢最大的红包

    文章目录 1.前言 2.微信红包模拟器 3.统计结果分析 4.小结 1.前言 微信红包我们天天都在抢,既然是抢红包,我们当然希望是能抢到越多越好,最好是能成为运气王,睥睨群芳.那么怎么才能成为运气王, ...

  3. python 发红包import random用redenv,Python微信发红包编码案例 微信发红包的架构模式 - push博客...

    Python手机微信红包优化算法案例 #!/usr/bin/env python # coding: utf-8 import random # m : 红包个数 # n : 红包人数 # packe ...

  4. 用Java写微信红包实现模拟微信发红包

    继承关系如下 题目: 某软件有多名用户(User类),某群聊中有群主(Manager类)和多名普通成员(Member类),现群主大方给成员发红包.红包的规则:群主发一笔金额,从群主余额中扣除,红包分配 ...

  5. h5拼手气红包java_Java模拟微信发红包(普通红包、拼手气红包)

    假设红包总额M元,分给N个人. 满足条件: 如果是普通红包,每个人获得的金额都一样: 如果是拼手气红包,则有所区别,但不能金额过于离谱,比如第一个获得的太多,以至于后来的人都几乎没得分. 注意点: 1 ...

  6. java运气红包_Java实现微信发红包

    前言 红包文化源远流长.从古时的红色纸包,到手机App中的手气红包,红包作为一种独特的中华文化传承至今.之前的所有内容中,综合Java这方面的知识,可以模拟发普通红包.那么这篇博客,我将整合之前介绍的 ...

  7. java微信红包_Java模拟微信发红包(普通红包、拼手气红包)

    假设红包总额M元,分给N个人. 满足条件: 如果是普通红包,每个人获得的金额都一样: 如果是拼手气红包,则有所区别,但不能金额过于离谱,比如第一个获得的太多,以至于后来的人都几乎没得分. 注意点: 1 ...

  8. 微信整人假红包图片_微信假红包生成器安卓-微信红包图片截图整人交友方法 红包显示一会出现你的交友宣言...

    这几天我发现微信是一个充满无限乐趣的好东西,不管是各种各样的GIF图还是朋友家人们分享的小视频,都会给我们闲暇生活中增添很多乐趣.微信动态图片制作 最近也是很流行呢!但是最近小编是沉浸在抢红包的&qu ...

  9. 微信整人假红包图片_微信假红包图片生成器,假红包生成器微信(玩别人没商量)...

    你要记住,无论最后我们疏远成什么样子,一个红包最能回到当初 .这段话在朋友圈很是流行,而且现在大家的聊天方式就是一言不合就发红包,惹女朋友生气了,发个红包就好了,亲朋好友的聊天群里,发个红包就能把潜水 ...

最新文章

  1. 盛大文学难逃“垄断”嫌疑,完美文学虎口夺食
  2. MIT+IBM同时利用AI探索神经科学,让脑科学研究如虎添翼
  3. 【Excel】VBA自动化更新数据表格
  4. mysql中int(16)_MySQL中int(M)和tinyint(M)数值类型中M值的意义
  5. 修改SAP下载文件路径
  6. 冒险岛无敌外挂代码诠释
  7. OneNET协议之LWM2M+CoAP
  8. 谷歌浏览器自带的翻译插件为什么不能用?
  9. 阿里云天池大数据竞赛——O2O优惠券使用预测(基于XGBoost)(附python Jupter代码)
  10. Dubblo +zookeep+sprinboot注册发现 (二)来源与狂神
  11. INFO:ProjectMgmt - The selected process was not run because a prior process failed.的解决方案
  12. 由IP6K防尘和IPX9K防水组合的IP6K9K
  13. 数据库操作的异常Cannot perform this operation because the connection pool has been close
  14. 唐巧iOS博客好文列表
  15. 一、计算机核心组成及CPU核心组成
  16. HTML和Css基础知识点笔记
  17. 我对计算机网络技术的理解,计算机网络技术教学总结
  18. pos机骗局收取押金如何投诉-真实案列解答
  19. 一个不用写代码的案例,来看看Flowable到底给我们提供了哪些功能?
  20. 分布式微服务学习总结——分布式微服务概述

热门文章

  1. /frameworks/support
  2. vs2012 MSDN帮助文档离线包下载安装方法
  3. oralce 创建用户和权限
  4. Activiti5工作流实战-4
  5. linux中程序包管理方式出现的原由(转载)
  6. optimizer_mode优化器模式
  7. EIGRP路由汇总与安全性配置
  8. [Java] 蓝桥杯 BASIC-2 基础练习 01字串
  9. 【软件测试】单元测试是软件测试的最基础环节
  10. L1-010. 比较大小-PAT团体程序设计天梯赛