惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Attack and Defense Labs
Attack and Defense Labs
T
Threatpost
C
Cybersecurity and Infrastructure Security Agency CISA
H
Hackread – Cybersecurity News, Data Breaches, AI and More
I
Intezer
C
Cyber Attacks, Cyber Crime and Cyber Security
The Register - Security
The Register - Security
量子位
Security Latest
Security Latest
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
大猫的无限游戏
大猫的无限游戏
小众软件
小众软件
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
C
CXSECURITY Database RSS Feed - CXSecurity.com
MyScale Blog
MyScale Blog
J
Java Code Geeks
Apple Machine Learning Research
Apple Machine Learning Research
Google DeepMind News
Google DeepMind News
WordPress大学
WordPress大学
Spread Privacy
Spread Privacy
Jina AI
Jina AI
博客园 - 【当耐特】
P
Palo Alto Networks Blog
Last Week in AI
Last Week in AI
SecWiki News
SecWiki News
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
G
GRAHAM CLULEY
宝玉的分享
宝玉的分享
Hacker News - Newest:
Hacker News - Newest: "LLM"
T
The Blog of Author Tim Ferriss
V
Vulnerabilities – Threatpost
有赞技术团队
有赞技术团队
T
Tor Project blog
H
Hacker News: Front Page
A
Arctic Wolf
NISL@THU
NISL@THU
A
About on SuperTechFans
云风的 BLOG
云风的 BLOG
Engineering at Meta
Engineering at Meta
V
V2EX
N
News and Events Feed by Topic
Webroot Blog
Webroot Blog
Know Your Adversary
Know Your Adversary
P
Privacy International News Feed
I
InfoQ
D
Docker
L
LINUX DO - 最新话题
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
U
Unit 42

博客园 - 斌哥tobin

Go工程选择开源分库分表中间件可用性测试 Golang解决fatal error: all goroutines are asleep - deadlock! Laravel Model查询结果的3种存储格式内存占用对比 Laravel配置Route调用artisan 解决IDEA提示Untrusted Server's certificate 证书不可用( Server's certificate is not trusted ) select * 和 select 字段的速度对比 Docker镜像拉取失败或超时的解决办法:添加国内镜像 php连接docker运行的mysql,显示(HY000/2002): Connection refused的解决办法 VirtualBox虚拟CentOS7共享文件夹 Windows7用VirtualBox虚拟Ubuntu共享文件夹的终极方式 PhpStorm添加PHP代码规范检查CodeSniffer(phpcs)和PHP代码静态分析工具Mess Detector(phpmd) php的三个常用判断函数 php计算字符串长度 mac brew 安装php扩展报错:parent directory is world writable but not sticky Mac iTerm2命令行快捷操作 System.Web.AspNetHostingPermission 类型的权限已失败 优化phpstorm运行卡顿问题! composer [ReflectionException] Class Fxp\Composer\AssetPlugin\Repository\NpmRepository does not exist - 斌哥tobin nginx1.8安装nginx_concat_module及400错误解决办法
研究微信红包分配算法之Golang版
斌哥tobin · 2020-02-17 · via 博客园 - 斌哥tobin

2020-02-17 10:33  斌哥tobin  阅读(2580)  评论()    收藏  举报

今天来看一下红包的分配,参考几年前流传的微信红包分配算法,今天用Golang实现一版,并测试验证结果。

微信红包的随机算法是怎样实现的?https://www.zhihu.com/question/22625187

红包核心算法

分配:红包里的金额怎么算?为什么出现各个红包金额相差很大?
答:随机,额度在0.01和(剩余平均值*2)之间

每次拆红包,额度范围在【0.01 ~ 剩余平均值*2】之间,这是很妙的一个设计。
比如发100元,共发10个红包,那么平均值10元,第一个拆出来的红包的额度在0.01元~20元之间波动,可以确保不会一个人把红包全领了的情况,因为最大就是剩余平均值的2倍。
比如发0.1元,共发10个红包,每个0.01元,这种就不用随机算法了,直接平均分配吧。

No bb, show your code!

设计红包结构体

//reward.go
//红包
type Reward struct {
	Count          int   //个数
	Money          int   //总金额(分)
	RemainCount    int   //剩余个数
	RemainMoney    int   //剩余金额(分)
	BestMoney int   //手气最佳金额
	BestMoneyIndex int   //手气最佳序号
	MoneyList      []int //拆分列表
}
  • 我这里用int整型做金额计算,可以避免浮点数精度问题,展示的时候除100,就是元单位了。

核心红包随机分配算法

//reward.go
// 抢红包
func GrabReward(reward *Reward) int {
	if reward.RemainCount <= 0 {
		panic("RemainCount <= 0")
	}
	//最后一个
	if reward.RemainCount - 1 == 0 {
		money := reward.RemainMoney
		reward.RemainCount = 0
		reward.RemainMoney = 0
		return money
	}`
	//是否可以直接0.01
	if (reward.RemainMoney / reward.RemainCount) == 1 {
		money := 1
		reward.RemainMoney -= money
		reward.RemainCount--
		return money
	}

	//红包算法参考 https://www.zhihu.com/question/22625187
	//最大可领金额 = 剩余金额的平均值x2 = (剩余金额 / 剩余数量) * 2
	//领取金额范围 = 0.01 ~ 最大可领金额
	maxMoney := int(reward.RemainMoney / reward.RemainCount) * 2
	rand.Seed(time.Now().UnixNano())
	money := rand.Intn(maxMoney)
	for money == 0 {
		//防止零
		money = rand.Intn(maxMoney)
	}
	reward.RemainMoney -= money
	//防止剩余金额负数
	if reward.RemainMoney < 0 {
		money += reward.RemainMoney
		reward.RemainMoney = 0
		reward.RemainCount = 0
	} else {
		reward.RemainCount--
	}
	return money
}

分配算法完成后,验证一下,用单元测试的办法验证

//reward_test.go
func TestGrabReward2(t *testing.T) {
	chanReward := make(chan Reward)
	rand.Seed(time.Now().UnixNano())
	go func(){
		//随机生成1000个红包
		for i:=0; i < 1000; i++  {
			//随机红包个数 1~50
			count := rand.Intn(50) + 1
			//随机红包总金额 1~100元
			money := rand.Intn(10000) + 100

			avg := money / count
			for avg == 0 {
				//保证金额足够分配
				count = rand.Intn(50) + 1
				money = rand.Intn(10000) + 100
				avg = money / count
			}
			reward := Reward{Count: count, Money: money,
				RemainCount: count, RemainMoney: money}

			chanReward <- reward
		}
		close(chanReward)
	}()

	//打印拆包列表,带手气最佳
	for reward := range chanReward {
		for i := 0; reward.RemainCount > 0; i++ {
			money := GrabReward(&reward)
			if money > reward.BestMoney {
				reward.BestMoneyIndex, reward.BestMoney = i, money
			}
			reward.MoneyList = append(reward.MoneyList, money)
		}
		t.Logf("总个数:%d, 总金额:%.2f", reward.Count, float32(reward.Money)/100)
		for i := range reward.MoneyList {
			money := reward.MoneyList[i]
			isBest := ""
			if reward.BestMoneyIndex == i {
				isBest = " ** 手气最佳"
			}
			t.Logf("money_%d : (%.2f)%s\n", i+1, float32(money)/100, isBest)
		}
		t.Log("-------")
	}

}

运行结果

    reward_test.go:106: 总个数:7, 总金额:86.59
    reward_test.go:113: money_1 : (16.29)
    reward_test.go:113: money_2 : (4.93)
    reward_test.go:113: money_3 : (22.89) ** 手气最佳
    reward_test.go:113: money_4 : (3.17)
    reward_test.go:113: money_5 : (20.51)
    reward_test.go:113: money_6 : (0.12)
    reward_test.go:113: money_7 : (18.68)
    reward_test.go:115: -------
    reward_test.go:106: 总个数:10, 总金额:53.79
    reward_test.go:113: money_1 : (3.56)
    reward_test.go:113: money_2 : (6.39)
    reward_test.go:113: money_3 : (0.36)
    reward_test.go:113: money_4 : (2.60)
    reward_test.go:113: money_5 : (10.11)
    reward_test.go:113: money_6 : (5.76)
    reward_test.go:113: money_7 : (2.84)
    reward_test.go:113: money_8 : (14.04) ** 手气最佳
    reward_test.go:113: money_9 : (1.95)
    reward_test.go:113: money_10 : (6.18)
    reward_test.go:115: -------

性能测试

//性能测试
func BenchmarkGrabReward(b *testing.B) {
	chanReward := make(chan *Reward, b.N)
	rand.Seed(time.Now().UnixNano())
	go func(){
		//随机生成红包
		for i:=0; i < b.N; i++  {
			//随机红包个数 1~50
			count := rand.Intn(50) + 1
			//随机红包总金额 1~100元
			money := rand.Intn(10000) + 100

			avg := money / count
			for avg == 0 {
				//保证金额足够分配
				count = rand.Intn(50) + 1
				money = rand.Intn(10000) + 100
				avg = money / count
			}
			reward := Reward{Count: count, Money: money,
				RemainCount: count, RemainMoney: money}

			chanReward <- &reward
		}
		close(chanReward)
	}()

	//打印拆包列表,带手气最佳
	for reward := range chanReward {
		for i := 0; reward.RemainCount > 0; i++ {
			money := GrabReward(reward)
			if money > reward.BestMoney {
				reward.BestMoneyIndex, reward.BestMoney = i, money
			}
			reward.MoneyList = append(reward.MoneyList, money)
		}
		_ = fmt.Sprintf("总个数:%d, 总金额:%.2f", reward.Count, float32(reward.Money)/100)
		for i := range reward.MoneyList {
			money := reward.MoneyList[i]
			isBest := ""
			if reward.BestMoneyIndex == i {
				isBest = " ** 手气最佳"
			}
			_ = fmt.Sprintf("money_%d : (%.2f)%s\n", i+1, float32(money)/100, isBest)
		}
	}
}

性能测试结果

BenchmarkGrabReward-8   	    4461	    244842 ns/op
//4核8线的CPU运运行4461次,平均每次244842纳秒=0.244842毫秒

性能可以说是很优秀的,这是因为这个测试是纯内存计算,没有网络IO,没有存储写盘,纯粹是为了验证算法,所以性能是很高的。
完成!