如何用 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, 禁止轉載 🈲️,侵權必究⚠️!



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM