面试官: 什么?你还不会发红包?

1,546 阅读3分钟

一直都很好奇微信红包的机制到底是怎样的,到底是什么原因运气王从来都不是我 [○・`Д´・ ○] 实在是忍不了了,那么在这片文章中,我们一起来看看,如何实现一个相对公平的红包算法。

红包的算法原理

红包是怎么发放的

我们都知道,发放一个多人红包时,每个人抢到红包金额是随机的,但是呢,哪怕是随机的,也可能不是同一杯羹。

  • 发放红包 我们首先会设置一个金额(total)和红包个数(num),点击发放红包的那一刻,这就已经定好了。
  • 思考一下,如果说 我们要发一个100元、10人的红包,我们怎么发呢?
  • 很快大家都想到 使用随机数Math.random(),我们知道radom的值是[0,1),可是10个红包的话,我们要用10个随机数吗?
  • 如果只是简单的10个 total*Math.random 那么总金额可能会超过我们的total,,并且根据概率论和相对公平性,每个人的金额都在平均值左右浮动。我们不难想:10个红包,把最后一个人的金额设置为total-前9个人的总和,那么这九个人的总和怎样才能小于total呢?
  • 这里我想到一个熟悉的公式:1>n=112n 1 > \sum_{n=1}^{\infty} \frac{1}{2^n}
  • 各位是否有了一点灵感呢? 如果每生成一个随机数amount 我们用剩余得的金额((Aamount-amount)/restNum)*Math.random,是否能达到类似的效果呢?
  • 大家都有想法了吧,这样的话每一个红包的金额都是在剩余的总钱数中取随机值了,成功实现了随机和等额。

先思而后行 我们代码实现

/**
 * 发红包算法
 * @param {number} total 总金额
 * @param {number} num 人数
 * @returns {Array} 返回一个数组,包含每个人的红包金额
 */
function hongbao(total, num) {
    //创建随机数组 储存随机金额
    const arr = [];
    // 余额
    let restAmount = total;
    // 剩余人数
    let restNum = num;
    //i < num - 1  只需要前面num-1个随机金额
    for (let i = 0; i < num - 1; i++) {
        //红包金额保留两位小数
        let amount = parseFloat(Math.random() * (restAmount / restNum)).toFixed(2);
        //得到红包内剩余金额和红包个数
        restAmount -= amount;
        restNum--;
        arr.push(amount);
    }
    // 最后一人的
    arr.push(restAmount.toFixed(2));
    return arr;
}

console.log(hongbao(100, 20));

我们来运行看看 试一试我们的运气

575d84f52498b7699061cb6ed8425167.png 确实做到了 但大家有没有发现,最后一个金额很大,显然这是不合理的。问题出在哪里呢?

优化

我们来看这一句

amount = parseFloat(Math.random() * (restAmount / restNum)).toFixed(2);

这是计算每个红包的公式,那么大家再想想,我之前提到的那个不等式。 再看看我们得出的结果数据,我们可以看出来,如果前面的红包金额太小了,往往会导致最后几个包的金额很大,那么如何修改呢,我们只需要这样,提高一下倍率。

let amount  =parseFloat(Math.random() *(restAmount/restNum)*2).toFixed(2)

f2a58993dae006c9f4069687ee020035.png

这样就不会出现两极分化的情况了,当然大家还可以试试,持续增加倍率会如何呢? 有兴趣 也可以看看 这篇文章 讲的更详细

总结

当然 我们日常使用的微信红包定然比这深奥得多,我们不过是小巫见大巫,但也让我们更好的去了解去思考红包机制,加油吧,路漫漫其修远兮,We将上下而求索。