最簡單的方法實現微信紅包的隨機算法🧧
微信紅包
微信紅包的隨機算法是怎樣實現的?
/**
算法需要滿足條件:
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) => {
const result = [...new Uint8Array(num)].fill(0.01, 0, num);
// 正態分布 ❓ TODO...
// 就地交換, shuffle
return shuffle(result);
}
const shuffle = (arr = []) => {
let len = arr.length;
while (len > 1){
// Math.floor
const index = Math.floor(Math.random() * len--);
// ES6 swap
[
arr[len],
arr[index],
] = [
arr[index],
arr[len],
];
}
return arr;
}
// 測試
const test = autoRandomRedPackage(100, 10);
log(`test =`, test)
partly OK
浮點數精度 bug
"use strict";
/**
*
* @author xgqfrms
* @license MIT
* @copyright xgqfrms
* @created 2020-09-16
* @modified
*
* @description 最簡單的方法實現微信紅包的隨機算法
* @difficulty Easy Medium Hard
* @complexity O(n)
* @augments
* @example
* @link https://www.zhihu.com/question/22625187/answer/1478941580
* @solutions
*
*/
const log = console.log;
const shuffle = (arr = []) => {
let len = arr.length;
while (len > 1){
// Math.floor
const index = Math.floor(Math.random() * len--);
// ES6 swap
[
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. 將計算好的數組,按照搶紅包的順序作為索引值依次取出紅包即可.✅
*/
/*
money = 100;
num = 11;
rest = money - 0.01 * num ;
99.89
99.89 / 0.01;
9989
9989 % 11
1
9988 / 11
908
9988 / 11 * 0.01
9.08
*/
const autoRandomRedPackage = (money, num, limit = 0.01) => {
const result = [...new Uint8Array(num)].fill(0.01, 0, num);
// 正態分布 ❓ TODO...
// 1. 將剩余的紅包,均分✅,如果有余數,隨機的添加到數組的一個元素上
const restLimit = (money - limit * num) / limit;
//9989
const reminderLimit = (restLimit % num);
// 1
const reminderMoney = reminderLimit * limit;
// 1 * 0.01
const averageLimit = (restLimit - reminderLimit) / num;
// 908
// const averageMoney = averageLimit * limit;
// 9.08
for (let i = 0; i < num; i++) {
const index = parseInt(Math.random() * averageLimit);
const randomMoney = index * limit;
log(`average index`, index, randomMoney)
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;
}
// 就地交換, shuffle
// return shuffle(result);
return {
total: shuffle(result).reduce((acc, i) => acc += i),// test total
result: shuffle(result),
};
}
// 測試
// const test = autoRandomRedPackage(100, 10);
const test = autoRandomRedPackage(100, 11);
log(`test =`, test);
/*
精度 bug
435 * 0.01
4.3500000000000005
35 * 0.01
0.35000000000000003
5 * 0.01
0.05
3 * 0.01
0.03
*/
/*
$ node red-package.js
average index 302 3.02
average index 615 6.15
average index 615 6.15
average index 416 4.16
average index 633 6.33
average index 96 0.96
average index 272 2.72
average index 830 8.3
average index 578 5.78
average index 701 7.01
average index 435 4.3500000000000005
test = {
total: 100,
result: [
8.32,
7.02,
9.09,
7.66,
8.41,
9.39,
11.670000000000002,
7.03,
4.17,
19.380000000000003,
7.859999999999999
]
}
*/
perfect OK
"use strict";
/**
*
* @author xgqfrms
* @license MIT
* @copyright xgqfrms
* @created 2020-09-16
* @modified
*
* @description 最簡單的方法實現微信紅包的隨機算法
* @difficulty Hard
* @complexity O(n)
* @augments
* @example
* @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) => {
// 精度損失解決方案, 擴大后,再還原❓✅🚀
money *= 100;
limit *= 100;
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;
// const averageMoney = averageLimit * limit;
for (let i = 0; i < num; i++) {
const index = parseInt(Math.random() * averageLimit);
const randomMoney = index * limit;
// log(`average index`, index, randomMoney);
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;
}
// 就地交換, shuffle
// return shuffle(result);
const total = shuffle(result).reduce((acc, i) => acc += i / 100, 0);
if(total !== 100) {
return autoRandomRedPackage(money / 100, num, limit / 100);
}
return {
total: total,// test total
result: shuffle(result).map(i => i / 100),
};
// return {
// total: shuffle(result).reduce((acc, i) => acc += i, 0),// test total
// result: shuffle(result),
// };
}
// 測試
const test = autoRandomRedPackage(100, 10);
// const test = autoRandomRedPackage(100, 11);
log(`test =`, test);
log(`total =`, test.result.reduce((acc, i) => acc += i, 0));
/*
$ node ok.js
test = {
total: 100,
result: [
8.47, 3.67, 6.77,
4.28, 2.15, 13.23,
9.24, 24.12, 20.32,
7.75
]
}
total = 100
*/
js double 精度損失 bugs
const arr = [
0.01, 0.01, 0.01,
0.01, 0.01, 0.01,
0.01, 0.01, 0.01,
0.01
];
// [0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]
arr.reduce((acc, i) => acc += i);
// 0.09999999999999999
arr = [
0.01, 0.01, 0.01,
0.01, 0.01, 0.01,
0.01, 0.01, 0.01,
0.01
]
(10) [0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]
arr.reduce((acc, i) => acc += i);
0.09999999999999999
sum = 0;
0
sum += 0.01;
0.01
sum += 0.01;
0.02
sum += 0.01;
0.03
sum += 0.01;
0.04
sum += 0.01;
0.05
sum += 0.01;
0.060000000000000005
sum += 0.01;
0.07
sum += 0.01;
0.08
sum += 0.01;
0.09
sum += 0.01;
0.09999999999999999
// 保留兩位精度 ?
js 小數轉整數
arr = [
0.01, 0.01, 0.01,
0.01, 0.01, 0.01,
0.01, 0.01, 0.01,
0.01
];
(10) [0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]
arr.map(i => i * 100).reduce((acc, i) => acc += i) / 100;
0.1
https://www.cnblogs.com/xgqfrms/p/13689522.html
refs
https://www.cnblogs.com/xgqfrms/p/13687916.html
https://www.cnblogs.com/xgqfrms/p/11977189.html
https://www.cnblogs.com/xgqfrms/p/13688278.html
https://www.zhihu.com/question/22625187/answer/1478941580
©xgqfrms 2012-2020
www.cnblogs.com 發布文章使用:只允許注冊用戶才可以訪問!
原創文章,版權所有©️xgqfrms, 禁止轉載 🈲️,侵權必究⚠️!