微信抢红包算法实现

微信抢红包算法实现

字节跳动二面问到了这个问题,小小研究了一下。(ps:微信红包貌似是使用二倍均值法实现的)

微信抢红包算法

只讨论金额随机的情况,需要满足规则:

  1. 所有人抢到金额之和要等于红包总金额1. 每个人至少抢到一分钱1. 要保证所有人抢到金额的几率相等 方案一:每个人点进来领,金额随机,随机的上限是当前剩余的红包金额。每次抢到的金额 = 随机区间(0,剩余红包金额)

分析:这样做的缺陷是越早领越有优势,因为每次抢到的金额 = 随机区间(0,剩余金额),越早抢能领到的平均金额越大。假设有 10 个人,红包总金额 100,第一个人的随机范围是(0,100),平均金额 = 50;假设第一个人随机到 50 元,第二个人的随机范围就是(0,50),平均金额 = 25;假设第二个人随机到 25 元,第三个人的随机范围就是(0,25),平均金额 = 12.5;以此类推,每一次的随机范围越来越小,平均金额也越小。

方案二:二倍均值法。设剩余红包金额为 M,剩余人数为 N,每次抢到的金额 = 随机区间(0,M / N * 2)

分析:这样保证了每个随机金额的平均值是相等的,不会因为抢红包的先后顺序而造成不公平。假设有 10 个人,红包总金额 100,第一个人的随机范围是(0,100/10 2)=(0,20),平均金额 = 10;假设第一个人随机到 10 元,第二个人的随机范围就是(0,90/9 2)=(0,20),平均金额 = 10;假设第二个人随机到 10 元,第三个人的随机范围就是(0,80/8 * 2)=(0,20),平均金额 = 10。以此类推,每一次的随机范围都相同,平均值也相同。二倍均值法保证了抢红包的公平性,但不能保证真正的随机性。因为除了最后一个人,前面任何一个人抢到的金额都一定小于当前人均金额的两倍,并不是真正的随机。

Python 代码实现

import random

def getPacket(amount, nums):
    limit = round(amount / nums * 2, 2)  # 求取上限(0,M / N * 2)
    print('随机金额范围:({}, {})'.format(0, limit))
    cur = round(random.uniform(0, limit), 2)
    return cur

if __name__ == '__main__':
    amount, nums = 100, 10
    cur_num = nums
    send = 0
    for i in range(cur_num):
        if i == nums-1:
            reward = amount
        else:
            reward = getPacket(amount, cur_num)
        amount = round(amount - reward, 2)
        send = round(send + reward, 2)
        cur_num -= 1
        print('第 {} 个人:{} 元'.format(i+1, reward))
        print('剩余金额:{} 元'.format(amount))
        print('已被领取:{} 元'.format(send))
        print('--------------------')

>>>
随机金额范围:(0, 20.0)
第 1 个人:0.32 元
剩余金额:99.68 元
已被领取:0.32 元
--------------------
随机金额范围:(0, 22.15)
第 2 个人:20.5 元
剩余金额:79.18 元
已被领取:20.82 元
--------------------
随机金额范围:(0, 19.8)
第 3 个人:8.3 元
剩余金额:70.88 元
已被领取:29.12 元
--------------------
随机金额范围:(0, 20.25)
第 4 个人:10.25 元
剩余金额:60.63 元
已被领取:39.37 元
--------------------
随机金额范围:(0, 20.21)
第 5 个人:7.01 元
剩余金额:53.62 元
已被领取:46.38 元
--------------------
随机金额范围:(0, 21.45)
第 6 个人:11.42 元
剩余金额:42.2 元
已被领取:57.8 元
--------------------
随机金额范围:(0, 21.1)
第 7 个人:15.78 元
剩余金额:26.42 元
已被领取:73.58 元
--------------------
随机金额范围:(0, 17.61)
第 8 个人:0.9 元
剩余金额:25.52 元
已被领取:74.48 元
--------------------
随机金额范围:(0, 25.52)
第 9 个人:8.11 元
剩余金额:17.41 元
已被领取:82.59 元
--------------------
第 10 个人:17.41 元
剩余金额:0.0 元
已被领取:100.0 元
--------------------

方案三:生成 k 个随机数,k 等于设置的分红包人数,保证 k 个随机数的和为 1,用总金额分别去乘这 k 个比例值,即可得到随机金额的 k 个红包,保证公平性和随机性。

import random

def getPacketRatio(nums):
    rand = [random.uniform(0, 5) for _ in range(nums)]
    sum = 0
    for i in range(nums):
        sum += rand[i]
    for i in range(nums):
        rand[i] = rand[i] / sum
    return rand

if __name__ == '__main__':
    amount, nums = 100, 10
    cur_amount, cur_num = amount, nums
    ratio = getPacketRatio(nums)
    #print(sum(ratio))
    send = 0
    for i in range(cur_num):
        if i == nums-1:
            reward = cur_amount
        else:
            #print('ratio = ', ratio[i])
            reward = round(amount * ratio[i], 2)
        cur_amount = round(cur_amount - reward, 2)
        send = round(send + reward, 2)
        print('第 {} 个人:{} 元'.format(i+1, reward))
        print('剩余金额:{} 元'.format(cur_amount))
        print('已被领取:{} 元'.format(send))
        print('--------------------')

>>>
ratio =  0.027962885102786654
第 1 个人:2.8 元
剩余金额:97.2 元
已被领取:2.8 元
--------------------
ratio =  0.009667867723838998
第 2 个人:0.97 元
剩余金额:96.23 元
已被领取:3.77 元
--------------------
ratio =  0.11110828265475124
第 3 个人:11.11 元
剩余金额:85.12 元
已被领取:14.88 元
--------------------
ratio =  0.0600812043281718
第 4 个人:6.01 元
剩余金额:79.11 元
已被领取:20.89 元
--------------------
ratio =  0.37838480938749
第 5 个人:37.84 元
剩余金额:41.27 元
已被领取:58.73 元
--------------------
ratio =  0.13730146852600614
第 6 个人:13.73 元
剩余金额:27.54 元
已被领取:72.46 元
--------------------
ratio =  0.1592426133137283
第 7 个人:15.92 元
剩余金额:11.62 元
已被领取:88.38 元
--------------------
ratio =  0.09194720453033237
第 8 个人:9.19 元
剩余金额:2.43 元
已被领取:97.57 元
--------------------
ratio =  0.008397562213245897
第 9 个人:0.84 元
剩余金额:1.59 元
已被领取:98.41 元
--------------------
第 10 个人:1.59 元
剩余金额:0.0 元
已被领取:100.0 元
--------------------
posted @ 2021-01-06 14:13  刘桓湚  阅读(787)  评论(0编辑  收藏  举报