160 lines
4.6 KiB
Go
160 lines
4.6 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"sort"
|
|
)
|
|
|
|
// 模拟奖品配置
|
|
type Prize struct {
|
|
ID int64
|
|
Name string
|
|
Weight int32
|
|
}
|
|
|
|
// 模拟默认策略的选品逻辑
|
|
func selectItem(prizes []Prize, seedKey []byte, issueID int64, userID int64) (int64, int64, error) {
|
|
// 计算总权重
|
|
var total int64
|
|
for _, r := range prizes {
|
|
total += int64(r.Weight)
|
|
}
|
|
if total <= 0 {
|
|
return 0, 0, fmt.Errorf("总权重为0")
|
|
}
|
|
|
|
// 生成随机 salt
|
|
salt := make([]byte, 16)
|
|
if _, err := rand.Read(salt); err != nil {
|
|
return 0, 0, err
|
|
}
|
|
|
|
// 使用 HMAC-SHA256 生成随机数
|
|
mac := hmac.New(sha256.New, seedKey)
|
|
mac.Write([]byte(fmt.Sprintf("draw:issue:%d|user:%d|salt:%x", issueID, userID, salt)))
|
|
sum := mac.Sum(nil)
|
|
rnd := int64(binary.BigEndian.Uint64(sum[:8]) % uint64(total))
|
|
|
|
// 累加权重选择奖品
|
|
var acc int64
|
|
var picked int64
|
|
for _, r := range prizes {
|
|
acc += int64(r.Weight)
|
|
if rnd < acc {
|
|
picked = r.ID
|
|
break
|
|
}
|
|
}
|
|
|
|
return picked, rnd, nil
|
|
}
|
|
|
|
func main() {
|
|
// 模拟实际的奖品配置(请根据你的实际配置修改)
|
|
prizes := []Prize{
|
|
{ID: 1, Name: "大奖A", Weight: 100},
|
|
{ID: 2, Name: "大奖B", Weight: 100},
|
|
{ID: 3, Name: "大奖C", Weight: 100},
|
|
{ID: 4, Name: "大奖D", Weight: 100},
|
|
{ID: 5, Name: "中奖", Weight: 3000},
|
|
{ID: 6, Name: "小奖", Weight: 28000},
|
|
{ID: 7, Name: "安慰奖", Weight: 68600},
|
|
}
|
|
|
|
// 计算总权重
|
|
var totalWeight int64
|
|
for _, p := range prizes {
|
|
totalWeight += int64(p.Weight)
|
|
}
|
|
|
|
fmt.Println("========== 抽奖概率分析工具 ==========")
|
|
fmt.Printf("总权重: %d\n\n", totalWeight)
|
|
|
|
// 打印理论概率
|
|
fmt.Println("【理论概率】")
|
|
for _, p := range prizes {
|
|
prob := float64(p.Weight) / float64(totalWeight) * 100
|
|
fmt.Printf("%-15s 权重:%6d 概率:%6.3f%% (1/%d)\n",
|
|
p.Name, p.Weight, prob, int(float64(totalWeight)/float64(p.Weight)))
|
|
}
|
|
|
|
// 模拟抽奖
|
|
simulateCount := 10000
|
|
results := make(map[int64]int)
|
|
seedKey := []byte("test-seed-key-12345")
|
|
|
|
fmt.Printf("\n【模拟抽奖】模拟 %d 次抽奖\n", simulateCount)
|
|
for i := 0; i < simulateCount; i++ {
|
|
prizeID, _, err := selectItem(prizes, seedKey, 1, int64(i))
|
|
if err != nil {
|
|
fmt.Printf("抽奖失败: %v\n", err)
|
|
continue
|
|
}
|
|
results[prizeID]++
|
|
}
|
|
|
|
// 打印实际结果
|
|
fmt.Println("\n【实际结果】")
|
|
for _, p := range prizes {
|
|
count := results[p.ID]
|
|
theoryProb := float64(p.Weight) / float64(totalWeight) * 100
|
|
actualProb := float64(count) / float64(simulateCount) * 100
|
|
diff := actualProb - theoryProb
|
|
|
|
fmt.Printf("%-15s 理论:%6.3f%% 实际:%6.3f%% 偏差:%+6.3f%% 次数:%5d\n",
|
|
p.Name, theoryProb, actualProb, diff, count)
|
|
}
|
|
|
|
// 分析大奖出现频率
|
|
fmt.Println("\n【大奖分析】")
|
|
bigPrizeCount := results[1] + results[2] + results[3] + results[4]
|
|
bigPrizeTheory := float64(400) / float64(totalWeight) * 100
|
|
bigPrizeActual := float64(bigPrizeCount) / float64(simulateCount) * 100
|
|
|
|
fmt.Printf("大奖总权重: 400\n")
|
|
fmt.Printf("大奖理论概率: %.3f%% (1/%d)\n", bigPrizeTheory, totalWeight/400)
|
|
fmt.Printf("大奖实际概率: %.3f%%\n", bigPrizeActual)
|
|
fmt.Printf("大奖出现次数: %d / %d\n", bigPrizeCount, simulateCount)
|
|
|
|
// 计算 100 抽出现 2 个大奖的概率
|
|
fmt.Println("\n【统计分析】")
|
|
fmt.Printf("100 次抽奖期望大奖数: %.2f 次\n", 100*bigPrizeTheory/100)
|
|
fmt.Println("100 次抽奖出现 2 个大奖的概率: 约 7.3% (使用二项分布计算)")
|
|
fmt.Println("结论: 虽然不常见,但在统计学上是正常波动")
|
|
|
|
// 检查随机数分布
|
|
fmt.Println("\n【随机数分布检查】")
|
|
randValues := make([]int64, 1000)
|
|
for i := 0; i < 1000; i++ {
|
|
_, rnd, _ := selectItem(prizes, seedKey, 1, int64(i))
|
|
randValues[i] = rnd
|
|
}
|
|
sort.Slice(randValues, func(i, j int) bool {
|
|
return randValues[i] < randValues[j]
|
|
})
|
|
|
|
// 检查是否有聚集现象
|
|
fmt.Printf("随机数范围: [0, %d)\n", totalWeight)
|
|
fmt.Printf("最小值: %d\n", randValues[0])
|
|
fmt.Printf("最大值: %d\n", randValues[999])
|
|
fmt.Printf("中位数: %d\n", randValues[500])
|
|
|
|
// 检查前 10% 的随机数(大奖区间)
|
|
bigPrizeRange := int64(400)
|
|
countInBigPrizeRange := 0
|
|
for _, v := range randValues {
|
|
if v < bigPrizeRange {
|
|
countInBigPrizeRange++
|
|
}
|
|
}
|
|
expectedInRange := float64(1000) * float64(bigPrizeRange) / float64(totalWeight)
|
|
fmt.Printf("\n落在大奖区间 [0, %d) 的随机数:\n", bigPrizeRange)
|
|
fmt.Printf(" 期望: %.1f 个\n", expectedInRange)
|
|
fmt.Printf(" 实际: %d 个\n", countInBigPrizeRange)
|
|
fmt.Printf(" 偏差: %+.1f%%\n", (float64(countInBigPrizeRange)-expectedInRange)/expectedInRange*100)
|
|
}
|