研究微信紅包分配演算法之Golang版
阿新 • • 發佈:2020-02-17
今天來看一下紅包的分配,參考幾年前流傳的微信紅包分配演算法,今天用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,沒有儲存寫盤,純粹是為了驗證演算法,所以效能是很高的。
完成