如何用 js 实现一个类似微信红包的随机算法 All In One
如何用 js 实现一个类似微信红包的随机算法 All In One
js, 微信红包, 随机算法
"use strict";
/**
*
* @author xgqfrms
* @license MIT
* @copyright xgqfrms
* @created 2020-09-16
* @modified
*
* @description 如何用 js 实现一个类似微信红包的随机算法, 最简单的方法实现微信红包的随机算法
* @difficulty Hard
* @complexity O(n)
* @augments
* @example
* @link https://www.cnblogs.com/xgqfrms/p/13689802.html
* @link https://www.cnblogs.com/xgqfrms/tag/%E7%BA%A2%E5%8C%85/
* @link https://www.zhihu.com/question/22625187/answer/1478941580
* @solutions
*
*/
const log = console.log;
const shuffle = (arr = []) => {
let len = arr.length;
while (len > 1){
const index = Math.floor(Math.random() * len--);
[
arr[len],
arr[index],
] = [
arr[index],
arr[len],
];
}
return arr;
}
/**
算法需要满足条件:
1. 每个人都可以分到至少 0.01 元;
2. 所有人的分到的红包之和与发出的金额相等,不多不少,刚好分完;
3. 每个人分到金额的概率相等;
*/
/**
假设,发出一个 100元红包,给 10个人分!
算法实现:
1. 按照人数,生成一个等长的数组,且每一个元素的初始化值为 0.01;✅
2. 将剩余的金额(100 - 10 * 0.01), 按照微信设计的规则(假如是正态分布)进行分配出 10 份; ❓
3. 将分配好的红包,依次加入到生成的数组中;✅
4. 最后使用 shuffle 算法打乱数组,并返回; ✅
5. 将计算好的数组,按照抢红包的顺序作为索引值依次取出红包即可.✅
*/
// 精度损失解决方案, 扩大后,再还原 ✅🚀
const autoRandomRedPackage = (money, num, limit = 0.01) => {
if((money / num) < limit) {
// alert
log(`💩 请重新输入红包数量! 减少红包数量,或增加红包金额!`);
log(`❌ 你输入的红包数量太多了,每个人至少要能分到 0.01 元!`);
return false;
} else {
const originMoney = money;
const originLimit = limit;
let multi = 100 * (100 / money);
money *= multi;
limit *= multi;
// log(`multi =`, multi);
const result = [...new Uint8Array(num)].fill(limit, 0, num);
// 1. 将剩余的红包,均分✅,如果有余数,随机的添加到数组的一个元素上
const restLimit = (money - limit * num) / limit;
const reminderLimit = (restLimit % num);
const reminderMoney = reminderLimit * limit;
const averageLimit = (restLimit - reminderLimit) / num;
for (let i = 0; i < num; i++) {
const index = parseInt(Math.random() * averageLimit);
const randomMoney = index * limit;
const leftMoney = (averageLimit - index) * limit;
// 2. 在平均后的范围内,计算出一个随机数,将分配好的红包,依次加入到生成的数组中;✅
result[i] += randomMoney;
// 3. 分配后剩余的红包,随机加入到生成的数组中;✅
const j = parseInt(Math.random() * num);
result[j] += leftMoney;
}
// 4. 将平均后的余数红包,随机加入到生成的数组中;✅
if(reminderMoney > 0) {
const index = parseInt(Math.random() * num);
result[index] += reminderMoney;
}
const temp = shuffle(result).map(i => i / multi);
// log(`temp =`, temp);
const total = temp.reduce((acc, i) => acc += i*multi, 0) / multi;
// log(`total !== originMoney`, total !== originMoney, total, originMoney);
if(total !== originMoney) {
return autoRandomRedPackage(originMoney, num, originLimit);
}
const [min, ...rest1] = temp.sort((a, b) => a - b > 0 ? 1 : -1);
const [max, ...rest2] = temp.sort((a, b) => a - b > 0 ? -1 : 1);
return {
total: total,
result: temp,
desc: `
🕵️♂️ 你输入的红包总额是 ${originMoney} 元, 红包数量是 ${num} 个!
👍 最大的红包是 ${max} 元!
👎 最小的红包是 ${min} 元!
`,
// desc: `
// 🕵️♂️ 你输入的红包总额是 ${originMoney} 元, 红包数量是 ${num} 个!\n
// 👍 最大的红包是 ${max} 元!\n
// 👎 最小的红包是 ${min} 元!\n
// `,
};
// return temp;
}
}
const total = arr => arr.result.reduce((acc, i) => acc += i*100, 0) / 100;
// 测试
const test = autoRandomRedPackage(0.1, 11);// ❌ 异常处理
const test0 = autoRandomRedPackage(0.1, 5);
const test1 = autoRandomRedPackage(0.1, 10); // ✅ ok
const test2 = autoRandomRedPackage(1, 10);
const test3 = autoRandomRedPackage(10, 10);
const test4 = autoRandomRedPackage(100, 10);
const test5 = autoRandomRedPackage(100, 11);
log(`\ntest =`, test);
log(`total =`, test && total(test));
log(`\ntest =`, test0);
log(`total =`, test0 && total(test0));
log(`\ntest =`, test1);
log(`total =`, test1 && total(test1));
log(`\ntest =`, test2);
log(`total =`, test2 && total(test2));
log(`\ntest =`, test3);
log(`total =`, test3 && total(test3));
log(`\ntest =`, test4);
log(`total =`, test4 && total(test4));
log(`\ntest =`, test5);
log(`total =`, test5 && total(test5));
/*
$ node perfect-solution.js
💩 请重新输入红包数量! 减少红包数量,或增加红包金额!
❌ 你输入的红包数量太多了,每个人至少要能分到 0.01 元!
test = false
total = false
test = {
total: 0.1,
result: [ 0.03, 0.03, 0.02, 0.01, 0.01 ],
desc: '\n' +
' 🕵️♂️ 你输入的红包总额是 0.1 元, 红包数量是 5 个!\n' +
' 👍 最大的红包是 0.03 元!\n' +
' 👎 最小的红包是 0.01 元!\n' +
' '
}
total = 0.1
test = {
total: 0.1,
result: [
0.01, 0.01, 0.01,
0.01, 0.01, 0.01,
0.01, 0.01, 0.01,
0.01
],
desc: '\n' +
' 🕵️♂️ 你输入的红包总额是 0.1 元, 红包数量是 10 个!\n' +
' 👍 最大的红包是 0.01 元!\n' +
' 👎 最小的红包是 0.01 元!\n' +
' '
}
total = 0.1
test = {
total: 1,
result: [
0.16, 0.13, 0.13,
0.12, 0.09, 0.08,
0.08, 0.08, 0.07,
0.06
],
desc: '\n' +
' 🕵️♂️ 你输入的红包总额是 1 元, 红包数量是 10 个!\n' +
' 👍 最大的红包是 0.16 元!\n' +
' 👎 最小的红包是 0.06 元!\n' +
' '
}
total = 1
test = {
total: 10,
result: [
2.43, 1.73, 1.43,
1.36, 1, 0.78,
0.66, 0.28, 0.25,
0.08
],
desc: '\n' +
' 🕵️♂️ 你输入的红包总额是 10 元, 红包数量是 10 个!\n' +
' 👍 最大的红包是 2.43 元!\n' +
' 👎 最小的红包是 0.08 元!\n' +
' '
}
total = 10
test = {
total: 100,
result: [
23.71, 16.9, 13.24,
12, 10, 8.6,
8.33, 3.6, 1.92,
1.7
],
desc: '\n' +
' 🕵️♂️ 你输入的红包总额是 100 元, 红包数量是 10 个!\n' +
' 👍 最大的红包是 23.71 元!\n' +
' 👎 最小的红包是 1.7 元!\n' +
' '
}
total = 100
test = {
total: 100,
result: [
17.59, 11.68, 11.55,
10.2, 8.83, 8.81,
8.24, 7.55, 7.44,
6.66, 1.45
],
desc: '\n' +
' 🕵️♂️ 你输入的红包总额是 100 元, 红包数量是 11 个!\n' +
' 👍 最大的红包是 17.59 元!\n' +
' 👎 最小的红包是 1.45 元!\n' +
' '
}
total = 100
*/
new version
v 1.1.0 bug fixed
https://www.npmjs.com/package/js-red-package/
"use strict";
/**
*
* @author xgqfrms
* @license MIT
* @copyright xgqfrms
* @created 2020-09-16
* @modified
*
* @description 如何用 js 实现一个类似微信红包的随机算法, 最简单的方法实现微信红包的随机算法
* @difficulty Hard
* @complexity O(n)
* @augments
* @example
* @link https://www.cnblogs.com/xgqfrms/p/13689802.html
* @link https://www.cnblogs.com/xgqfrms/tag/%E7%BA%A2%E5%8C%85/
* @link https://www.zhihu.com/question/22625187/answer/1478941580
* @solutions
*
*/
const log = console.log;
const shuffle = (arr = []) => {
let len = arr.length;
while (len > 1){
const index = Math.floor(Math.random() * len--);
[
arr[len],
arr[index],
] = [
arr[index],
arr[len],
];
}
return arr;
}
/**
算法需要满足条件:
1. 每个人都可以分到至少 0.01 元;
2. 所有人的分到的红包之和与发出的金额相等,不多不少,刚好分完;
3. 每个人分到金额的概率相等;
*/
/**
假设,发出一个 100元红包,给 10个人分!
算法实现:
1. 按照人数,生成一个等长的数组,且每一个元素的初始化值为 0.01;✅
2. 将剩余的金额(100 - 10 * 0.01), 按照微信设计的规则(假如是正态分布)进行分配出 10 份; ❓
3. 将分配好的红包,依次加入到生成的数组中;✅
4. 最后使用 shuffle 算法打乱数组,并返回; ✅
5. 将计算好的数组,按照抢红包的顺序作为索引值依次取出红包即可.✅
*/
const autoRandomRedPackage = (money, num, limit = 0.01) => {
if((money / num) < limit) {
log(`💩 请重新输入红包数量! 减少红包数量,或增加红包金额!`);
log(`❌ 你输入的红包数量太多了,每个人至少要能分到 0.01 元!`);
return false;
} else {
const originMoney = money;
const originLimit = limit;
// 精度损失解决方案, 扩大后,再还原 ✅🚀
let multi = 100 * (100 / money);
money *= multi;
limit *= multi;
// log(`multi =`, multi);
const result = [...new Uint8Array(num)].fill(limit, 0, num);
// 1. 将剩余的红包,均分✅,如果有余数,随机的添加到数组的一个元素上
const restLimit = (money - limit * num) / limit;
const reminderLimit = (restLimit % num);
const reminderMoney = reminderLimit * limit;
const averageLimit = (restLimit - reminderLimit) / num;
for (let i = 0; i < num; i++) {
const index = parseInt(Math.random() * averageLimit);
const randomMoney = index * limit;
const leftMoney = (averageLimit - index) * limit;
// 2. 在平均后的范围内,计算出一个随机数,将分配好的红包,依次加入到生成的数组中;✅
result[i] += randomMoney;
// 3. 分配后剩余的红包,随机加入到生成的数组中;✅
const j = parseInt(Math.random() * num);
result[j] += leftMoney;
}
// 4. 将平均后的余数红包,随机加入到生成的数组中;✅
if(reminderMoney > 0) {
const index = parseInt(Math.random() * num);
result[index] += reminderMoney;
}
const temp = shuffle(result).map(i => i / multi);
// log(`temp =`, temp);
const total = temp.reduce((acc, i) => acc += i*multi, 0) / multi;
// log(`total !== originMoney`, total !== originMoney, total, originMoney);
if(total !== originMoney) {
return autoRandomRedPackage(originMoney, num, originLimit);
}
const [min,] = [...temp].sort((a, b) => a - b > 0 ? 1 : -1);
const [max,] = [...temp].sort((a, b) => a - b > 0 ? -1 : 1);
// const [min, ...rest1] = [...temp].sort((a, b) => a - b > 0 ? 1 : -1);
// const [max, ...rest2] = [...temp].sort((a, b) => a - b > 0 ? -1 : 1);
// sort change origin array order bug ❌
// const [min, ...rest1] = temp.sort((a, b) => a - b > 0 ? 1 : -1);
// const [max, ...rest2] = temp.sort((a, b) => a - b > 0 ? -1 : 1);
return {
total: total,
result: temp,
desc: `
🕵️♂️ 你输入的红包总额是 ${originMoney} 元, 红包数量是 ${num} 个!
👍 最大的红包是 ${max} 元!
👎 最小的红包是 ${min} 元!
`,
// desc: `
// 🕵️♂️ 你输入的红包总额是 ${originMoney} 元, 红包数量是 ${num} 个!\n
// 👍 最大的红包是 ${max} 元!\n
// 👎 最小的红包是 ${min} 元!\n
// `,
};
// return temp;
}
}
const total = arr => arr.result.reduce((acc, i) => acc += i*100, 0) / 100;
// 测试
const test = autoRandomRedPackage(0.1, 11);// ❌ 异常处理
const test0 = autoRandomRedPackage(0.1, 5);
const test1 = autoRandomRedPackage(0.1, 10); // ✅ ok
const test2 = autoRandomRedPackage(1, 10);
const test3 = autoRandomRedPackage(10, 10);
const test4 = autoRandomRedPackage(100, 10);
const test5 = autoRandomRedPackage(100, 11);
log(`\ntest =`, test);
log(`total =`, test && total(test));
log(`\ntest =`, test0);
log(`total =`, test0 && total(test0));
log(`\ntest =`, test1);
log(`total =`, test1 && total(test1));
log(`\ntest =`, test2);
log(`total =`, test2 && total(test2));
log(`\ntest =`, test3);
log(`total =`, test3 && total(test3));
log(`\ntest =`, test4);
log(`total =`, test4 && total(test4));
log(`\ntest =`, test5);
log(`total =`, test5 && total(test5));
/*
$ node index.js
(node:44044) ExperimentalWarning: The ESM module loader is experimental.
💩 请重新输入红包数量! 减少红包数量,或增加红包金额!
❌ 你输入的红包数量太多了,每个人至少要能分到 0.01 元!
test = false
total = false
test = {
total: 0.1,
result: [ 0.01, 0.02, 0.02, 0.03, 0.02 ],
desc: '\n' +
' 🕵️♂️ 你输入的红包总额是 0.1 元, 红包数量是 5 个!\n' +
' 👍 最大的红包是 0.03 元!\n' +
' 👎 最小的红包是 0.01 元!\n' +
' '
}
total = 0.1
test = {
total: 0.1,
result: [
0.01, 0.01, 0.01,
0.01, 0.01, 0.01,
0.01, 0.01, 0.01,
0.01
],
desc: '\n' +
' 🕵️♂️ 你输入的红包总额是 0.1 元, 红包数量是 10 个!\n' +
' 👍 最大的红包是 0.01 元!\n' +
' 👎 最小的红包是 0.01 元!\n' +
' '
}
total = 0.1
test = {
total: 1,
result: [
0.16, 0.04, 0.07,
0.27, 0.11, 0.06,
0.02, 0.14, 0.07,
0.06
],
desc: '\n' +
' 🕵️♂️ 你输入的红包总额是 1 元, 红包数量是 10 个!\n' +
' 👍 最大的红包是 0.27 元!\n' +
' 👎 最小的红包是 0.02 元!\n' +
' '
}
total = 1
test = {
total: 10,
result: [
1.19, 1.63, 0.37,
0.27, 1.8, 1.01,
0.62, 0.38, 1.92,
0.81
],
desc: '\n' +
' 🕵️♂️ 你输入的红包总额是 10 元, 红包数量是 10 个!\n' +
' 👍 最大的红包是 1.92 元!\n' +
' 👎 最小的红包是 0.27 元!\n' +
' '
}
total = 10
test = {
total: 100,
result: [
5.62, 12.59, 29.17,
19.11, 7.33, 9.91,
0.89, 2.36, 7.66,
5.36
],
desc: '\n' +
' 🕵️♂️ 你输入的红包总额是 100 元, 红包数量是 10 个!\n' +
' 👍 最大的红包是 29.17 元!\n' +
' 👎 最小的红包是 0.89 元!\n' +
' '
}
total = 100
test = {
total: 100,
result: [
2.88, 7.24, 8.7,
10.53, 12.8, 29.59,
6.02, 5.92, 0.49,
8.9, 6.93
],
desc: '\n' +
' 🕵️♂️ 你输入的红包总额是 100 元, 红包数量是 11 个!\n' +
' 👍 最大的红包是 29.59 元!\n' +
' 👎 最小的红包是 0.49 元!\n' +
' '
}
total = 100
*/
npm
https://www.npmjs.com/package/js-red-package/
// Step 1: Use `publishConfig` option in your package.json
"publishConfig": { "registry": "https://npm.pkg.github.com/" }
// Step 2: Authenticate
$ npm login --registry=https://npm.pkg.github.com/
// Step 3: Publish
$ npm publish
https://github.com/xgqfrms/js-red-package/packages
https://github.com/xgqfrms/js-red-package/
refs
https://www.cnblogs.com/xgqfrms/tag/红包/
©xgqfrms 2012-2020
www.cnblogs.com/xgqfrms 发布文章使用:只允许注册用户才可以访问!
原创文章,版权所有©️xgqfrms, 禁止转载 🈲️,侵权必究⚠️!
本文首发于博客园,作者:xgqfrms,原文链接:https://www.cnblogs.com/xgqfrms/p/13689802.html
未经授权禁止转载,违者必究!