Go语言实现加权随机抽奖功能


很多时候我们需要实现一个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)
}

算法说明

  1. 将概率转换为整数权重(乘以10000避免浮点数运算)
  2. 计算总权重(10000)
  3. 生成0到总权重-1的随机数
  4. 根据随机数落在哪个权重区间决定输出结果

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万次)后,统计结果应该接近设定的概率。

总结

  1. 基础权重随机算法 - 简单直接,适合大多数场景
  2. 安全随机数版本 - 适合安全敏感场景
  3. 第三方库实现 - 简化开发,适合快速实现
  4. 并发安全版本 - 适合高并发环境

所有实现都遵循相同的核心逻辑:将概率转换为权重,生成随机数,然后根据随机数落在哪个权重区间决定输出结果。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值