很多时候我们需要实现一个Go语言的抽奖功能:
比如:按照20.23%的概率输出1,31.56%的概率输出2,否则输出3。(当然这里可以用你)
下面我将提供几种实现方式,并解释其原理。
1. 权重随机算法实现
这是最直接的方法,使用权重随机算法来实现的需求:
package main
import (
"fmt"
"math/big"
"math/rand"
"time"
)
func weightedRandom() int {
// 使用crypto/rand生成更安全的随机数
rand.Seed(time.Now().UnixNano())
// 将概率转换为权重(乘以10000避免浮点数运算)
weights := []int{2023, 3156, 4821} // 4821 = 10000 - 2023 - 3156
totalWeight := 10000
// 生成随机数
randomNum := rand.Intn(totalWeight)
// 确定结果
if randomNum < weights[0] {
return 1
} else if randomNum < weights[0]+weights[1] {
return 2
} else {
return 3
}
}
func main() {
// 测试10000次,验证概率分布
counts := make(map[int]int)
trials := 10000
for i := 0; i < trials; i++ {
result := weightedRandom()
counts[result]++
}
fmt.Printf("测试结果(%d次):\n", trials)
fmt.Printf("1出现的次数: %d (%.2f%%)\n", counts[1], float64(counts[1])/float64(trials)*100)
fmt.Printf("2出现的次数: %d (%.2f%%)\n", counts[2], float64(counts[2])/float64(trials)*100)
fmt.Printf("3出现的次数: %d (%.2f%%)\n", counts[3], float64(counts[3])/float64(trials)*100)
}
算法说明
- 将概率转换为整数权重(乘以10000避免浮点数运算)
- 计算总权重(10000)
- 生成0到总权重-1的随机数
- 根据随机数落在哪个权重区间决定输出结果
2. 使用更安全的随机数生成器
如果需要更高的安全性(如防止预测),可以使用crypto/rand代替math/rand:
func secureWeightedRandom() (int, error) {
// 将概率转换为权重(乘以10000避免浮点数运算)
weights := []int{2023, 3156, 4821} // 4821 = 10000 - 2023 - 3156
totalWeight := big.NewInt(10000)
// 使用crypto/rand生成安全随机数
randomNum, err := rand.Int(rand.Reader, totalWeight)
if err != nil {
return 0, err
}
r := randomNum.Int64()
if r < int64(weights[0]) {
return 1, nil
} else if r < int64(weights[0]+weights[1]) {
return 2, nil
} else {
return 3, nil
}
}
这种方法更适合需要防止作弊的安全敏感场景。
3. 使用第三方库weightedrand
如果想使用现成的库,可以考虑weightedrand:
package main
import (
"fmt"
"github.com/mroth/weightedrand"
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().UTC().UnixNano())
chooser, _ := weightedrand.NewChooser(
weightedrand.Choice{Item: 1, Weight: 2023},
weightedrand.Choice{Item: 2, Weight: 3156},
weightedrand.Choice{Item: 3, Weight: 4821},
)
// 使用示例
result := chooser.Pick()
fmt.Println(result)
}
这种方法简化了实现,适合快速开发。
4. 并发安全版本
如果需要在高并发环境下使用,可以添加互斥锁:
type ConcurrentLottery struct {
mu sync.Mutex
}
func (cl *ConcurrentLottery) Draw() int {
cl.mu.Lock()
defer cl.mu.Unlock()
weights := []int{2023, 3156, 4821}
totalWeight := 10000
randomNum := rand.Intn(totalWeight)
if randomNum < weights[0] {
return 1
} else if randomNum < weights[0]+weights[1] {
return 2
}
return 3
}
这种方法可以防止并发抽奖导致的数据竞争问题。
概率验证
为了确保实现的正确性,可以运行概率验证测试:
func TestProbabilityDistribution(t *testing.T) {
counts := make(map[int]int)
trials := 1000000
for i := 0; i < trials; i++ {
result := weightedRandom()
counts[result]++
}
t.Logf("1: %.4f%%", float64(counts[1])/float64(trials)*100)
t.Logf("2: %.4f%%", float64(counts[2])/float64(trials)*100)
t.Logf("3: %.4f%%", float64(counts[3])/float64(trials)*100)
}
运行足够多次数(如100万次)后,统计结果应该接近设定的概率。
总结
- 基础权重随机算法 - 简单直接,适合大多数场景
- 安全随机数版本 - 适合安全敏感场景
- 第三方库实现 - 简化开发,适合快速实现
- 并发安全版本 - 适合高并发环境
所有实现都遵循相同的核心逻辑:将概率转换为权重,生成随机数,然后根据随机数落在哪个权重区间决定输出结果。
545

被折叠的 条评论
为什么被折叠?



