最简单的方法实现微信红包的随机算法


最简单的方法实现微信红包的随机算法🧧

微信红包

微信红包的随机算法是怎样实现的?

/**

算法需要满足条件:
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, 禁止转载 🈲️,侵权必究⚠️!



免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM