年会抢红包策略
[Misc]
0x01 游戏规则¶
该游戏名叫红包接龙,规则如下:
年会会场内所有人都通过钉钉群的方式参与该游戏,会场人数一般为200~300人(大部分能时候是超过红包最大拆分份数):
- 由老板发出第一个种子红包,金额 b = 500,红包分成100份,每份金额是随机的,红包发到钉钉群后,大家可以由两种选择:抢 or 不抢;
- 如果选择不抢,则本轮无损失也没有收益。
- 如果选择了抢,还需要拼手速,因为大部分时候选择抢的同学个数依然大于红包最大拆分份数;
- 所有红包拆分份数都被抢完之后,由本轮抢的最大金额的同学发下轮的红包,每轮的红包金额需要在之前的基础上增加200;
- 游戏循环进行,直到达到5000元上限结束;
0x02 红包算法¶
红包算法采用min,max之间随机生成:
- min=0.01(一分钱)
- max=剩余金额*2/剩余红包数
In [1]:
import random import math import numpy as np class redpacket: def __init__(self, money, part): self.remain_money = money self.remain_size = part def get_random_money(self): if self.remain_size == 1: self.remain_size -= 1 return round(self.remain_money * 100) / 100.0 r = random.random() min = 0.01 max = self.remain_money * 2 / float(self.remain_size) money = r * max money = money if money >= min else min money = math.floor(money * 100) / 100.0 self.remain_size -= 1 self.remain_money -= money return money def predict(self): ans = [] for i in range(self.remain_size): current_round = self.get_random_money() ans.append(current_round) return ans
In [2]:
print redpacket(100,10).predict()
[16.22, 13.73, 9.48, 4.35, 0.95, 2.8, 11.64, 22.79, 8.55, 9.49]
0x03 领取顺序带来的影响¶
为了量化收益与领取红包的顺序的关系,我们按照领取顺序将用户分为1-10号,在总额100的状态下进行1000次试验,得到结论:
- 收益期望不会被顺序影响,但是方差会,也就是说越靠后领取的红包数值波动越大
In [ ]:
from pyecharts import Bar user_cnt = 10 times = 1000 test1k = [redpacket(100,10).predict() for i in range(times)] attr = ['user_{}'.format(i+1) for i in range(user_cnt)] v1 = np.around(np.average(test1k, axis=0), 2) bar = Bar('收益期望') bar.add('',attr,v1,mark_line=["average"],is_label_show=True,label_text_color='#000') bar
In [ ]:
from pyecharts import Scatter user_cnt = 10 times = 1000 ans_sca = np.array([redpacket(100,10).predict() for i in range(times)]) scatter = Scatter("收益散点图") v1 = [] v2 = [] for i in range(user_cnt): v1.extend([i+1]*times) v2.extend(ans_sca[:,i]) scatter.add("", v1, v2,symbol_size=3) scatter
由散点图可以看出,越靠后领取的红包金额波动(方差)越大,在1号用户视角,由于max值被限制在奖池余额*2/剩余红包个数 = 100*2/10 = 20
之内,因此1号用户领取的金额只能在[0.01,20.]之内波动;而10号用户的波动区间更大,假设前9人全都是0.01,那10号可以领到99.91。
- 红包金额波动会对游戏策略带来什么影响?我们加入游戏规则中的“处罚策略”,实现完整的实验代码。
In [6]:
class game: def __init__(self, people, round, start_money, step): self.people = people self.round = round self.start_money = start_money self.note = np.array([0] * people, dtype=float) self.step = step def start_game(self): for i in range(self.round): r = redpacket(self.start_money, self.people) ans = r.predict() self.note += np.array(ans, dtype=float) self.start_money += self.step # 处罚策略:本轮抢的最大金额的同学发下轮的红包,每轮的红包金额需要在之前的基础上增加N; if i < self.round - 1: # 最后一轮不处罚 best_score = max(ans) # print 'round:{},best:{}'.format(i, ans.index(best_score)) self.note[ans.index(best_score)] -= self.start_money return self.note
实验:10个人,初始老板发一个500元的奖池,共30轮抽奖,每次抽到最大红包的人加200元成为下一轮奖池。
10000次实验的收益期望如下:
In [ ]:
from pyecharts import Bar user_cnt = 10 times = 10000 result = [game(user_cnt, 30, start_money=500, step=200).start_game() for i in range(times)] attr = ['user_{}'.format(i+1) for i in range(user_cnt)] v1 = np.around(np.average(result, axis=0), 2) bar = Bar('收益期望') bar.add('',attr,v1,mark_line=["average"],is_label_show=True,label_text_color='#000') bar
按照初始奖池500/10=50是总体收益的期望,我们可以看出收益是先拉升最终再降低,结果是便于理解的。
- 头部的节点,由于波动区间小,且靠近底部,每次只能拿到一个偏低的数额。
- 尾部的节点,由于波动范围大,如果拿到小数额那么收益有限,如果拿到大数额则很可能被处罚(下一轮发红包),因此尾部属于慈善玩家。
因此,如果我们能控制抢红包的顺序的话,可以在中前部出手,收益略高。具体位置需要通过人数、起步奖池、轮数、处罚金额来计算
0x04 自动抢红包脚本入场?¶
如果我们能用程序在每有一个人领取红包之后,迅速计算全盘状态并给出决策,会有什么变化?
- 如果可以准确控制我们的入场位置,则可以计算出"绝对安全"的领取时机。
想象10个人抢100元红包的情况,第一个人直接命中了当前位置的上限20,后面180奖池+9人,下一轮max值=80*2/9<20,所以下一个领取红包的人无论抢多少,都不会成为本局最大值被处罚。
改进一下predict的过程,打印出每个位序是"绝对安全"(1)或者"有可能被惩罚"(0)
In [8]:
class redpacket: def __init__(self, money, part): self.remain_money = money self.remain_size = part def get_random_money(self): if self.remain_size == 1: self.remain_size -= 1 return round(self.remain_money * 100) / 100.0 r = random.random() min = 0.01 max = self.remain_money * 2 / float(self.remain_size) money = r * max money = money if money >= min else min money = math.floor(money * 100) / 100.0 self.remain_size -= 1 self.remain_money -= money return money def predict(self): ans = [] ans_print = [] for i in range(self.remain_size): ans_max = max(ans) if ans else 0 if (self.remain_money * 2 / float(self.remain_size)) < ans_max: ans_print.append(1) # 安全 else: ans_print.append(0) current_round = self.get_random_money() ans.append(current_round) return ans, ans_print
In [9]:
for i in range(5): print redpacket(100,10).predict()
([0.05, 8.87, 15.79, 7.49, 8.27, 5.09, 22.44, 11.64, 4.23, 16.13], [0, 0, 0, 0, 0, 0, 0, 1, 1, 0]) ([1.29, 17.17, 1.16, 14.51, 12.3, 10.46, 20.98, 3.46, 5.78, 12.89], [0, 0, 0, 0, 0, 0, 0, 1, 1, 0]) ([3.09, 15.17, 15.21, 4.71, 13.02, 8.22, 16.7, 11.22, 7.1, 5.56], [0, 0, 0, 0, 0, 0, 0, 1, 1, 1]) ([18.5, 7.61, 14.32, 4.55, 13.31, 1.73, 0.82, 18.35, 18.03, 2.78], [0, 1, 1, 1, 1, 1, 0, 0, 0, 1]) ([3.69, 7.76, 5.67, 23.51, 4.77, 12.9, 6.38, 16.78, 17.2, 1.34], [0, 0, 0, 0, 1, 1, 1, 0, 1, 1])
In [10]:
test = [1 if 1 in redpacket(100,10).predict()[-1] else 0 for i in range(10000)] print sum(test)/10000.0
0.8699
- 从结果来看,在87%的游戏中我们可以等到"绝对安全"的位置出现。