研究微信紅包分配算法之Golang版


今天來看一下紅包的分配,參考幾年前流傳的微信紅包分配算法,今天用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,沒有存儲寫盤,純粹是為了驗證算法,所以性能是很高的。
完成!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM