面试场景题:如何实现带权重值的抽奖程序
背景:
在之前有遇到一个面试题,让实现一个带有权重值的抽奖系统,具体如下:
假设一个类似抽盲盒的场景,配置了三个物品,1,2,3,分别的权重是20,30,50,表明物品1中奖概率20%,物品2中奖概率30%,物品3中奖概率50%,用户买了这个盲盒,实现根据中奖概率返回哪件物品?
infos := []RewardInfo{{RewardId: 1, RewardName: "物品A", Weight: 20},// 20%{RewardId: 2, RewardName: "物品B", Weight: 30},// 30%{RewardId: 3, RewardName: "物品C", Weight: 50},// 50%}
大家可以先想一下具体要怎么实现?
以穿越火线官方活动为例,活动链接:cf道聚城活动:
思路:
前提:以下代码示例都是参考上图的权重值。
- 首先,求出能使得所有奖品权重值都变为正整数的最大倍数
maxMultiple
- 找出所有奖品对应的权重值中,小数点后 [数字位数最多的权重值],并根据此权重值小数点后的位数,求出使其权重值变为正整数的最大倍数
maxMultiple
。遍历过程中,我们只关注小数类型的权重值,整数类型的权重值由于没有小数点,可以直接跳过。 - 例如:“奖品A权重值 0.25,奖品B权重值 0.75, 奖品C权重值 1,奖品D权重值 98,加起来一共是 100 ”。我们可以找出 小数点后 [数字位数最多的权重值],即 奖品A的 0.25(或者 奖品B的权重值 0.75)。并且其小数点之后的位数有2位,它需要乘以100(10*10)才能变为正整数 25,因此得到
maxMultiple = 100
。
- 找出所有奖品对应的权重值中,小数点后 [数字位数最多的权重值],并根据此权重值小数点后的位数,求出使其权重值变为正整数的最大倍数
- 然后,根据 [不同奖品的权重值] 乘以 [之前求得的最大倍数
maxMultiple
],分配对应个数的奖品到奖池中: - 最后,根据 rand 随机种子函数,从构建好的奖池中抽取奖品即可。
如果需要的话,可以将每次抽取的奖品记录单独输出到一个文件中。
注意:
rewardList := make([]int32, 0, maxMultiple*100)
:
为了避免奖池切片反复扩容影响性能,最好提前预估cap容量:倍率*100
例如奖品A的权重为 0.25,乘以100倍率后为 25;奖品D的权重为 98,乘以100倍率后为 9800;那么对于总权重为100,乘以100倍率后就为1w。因此可以预估切片容量为1w。time.Sleep(time.Nanosecond)
:
每次抽奖都有一定时间间隔,避免程序运行过快导致一瞬间的随机数种子都一致,我这里每次休眠时长为100纳秒。
更多优化点:
为了避免切片占据过大的内存空间,可以考虑使用 BitMap位图 来代替常规切片数组,来减少内存开销,大家可以自己尝试下如何实现。
代码:
// RewardInfo 奖励信息
type RewardInfo struct {RewardId int32 `json:"-"` // 奖品idRewardName string `json:"RewardName"` // 奖品名称Weight float32 `json:"Weight"` // 抽中的权重(省略末尾百分号的形式:例如某奖品权重为 0.1%,那么这里值也为 0.1)GetCnt int32 `json:"GetCnt"` // 中奖数
}// 抽奖模拟器
// 入参:chouJiangCnt-用户的抽取次数
func LotterySimulator(chouJiangCnt int) {// 以下权重都是百分制(省略了末尾百分号%),例如 0.1 → 0.1%,79 → 79%,一共是100%infos := []RewardInfo{{RewardId: 1, RewardName: "幻神-CFS2022(皮肤,不可交易)", Weight: 0.1},{RewardId: 2, RewardName: "火麒麟-巅峰荣耀(皮肤,不可交易)", Weight: 0.15},{RewardId: 3, RewardName: "黑骑士-CFS2021(皮肤,不可交易)", Weight: 0.25},{RewardId: 4, RewardName: "毁灭-CFS2020(皮肤,不可交易)", Weight: 0.25},{RewardId: 5, RewardName: "M4A1-雷神-CFS2019(皮肤,不可交易)", Weight: 0.3},{RewardId: 6, RewardName: "毁灭-CFS2018(皮肤,不可交易)", Weight: 0.3},{RewardId: 7, RewardName: "火麒麟-CFS2018(皮肤,不可交易)", Weight: 0.3},{RewardId: 8, RewardName: "屠龙-荣耀之锋(皮肤,不可交易)", Weight: 0.4},{RewardId: 9, RewardName: "修罗-CFS2018(皮肤,不可交易)", Weight: 0.85},{RewardId: 10, RewardName: "王者之石x1", Weight: 7},{RewardId: 11, RewardName: "积分x100", Weight: 0.1},{RewardId: 12, RewardName: "积分x10", Weight: 1},{RewardId: 13, RewardName: "积分x5", Weight: 10},{RewardId: 14, RewardName: "积分x1", Weight: 79},}// 初始化奖品属性maprewardMap := make(map[int32]RewardInfo)for _, rewardInfo := range infos {rewardMap[rewardInfo.RewardId] = RewardInfo{RewardId: rewardInfo.RewardId,RewardName: rewardInfo.RewardName,Weight: rewardInfo.Weight,GetCnt: 0,}}// 此处按需添加:将抽奖结果记录文件于当前路径下// 注意:我下面是判断抽到幻神才输出结果记录到文件testJsonFile, _ := os.OpenFile("./cf_reward.json", os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777)defer testJsonFile.Close()// 当前抽奖已花费总金额,初始为0;单次抽奖花费金额10元costMoneyOfTotal, costMoneyOfEveryTime := 0, 10for {time.Sleep(time.Second) // 每秒抽一次,控制下速率,方便控制台打印观察fmt.Printf("抽奖中...,当前已消费%v元\n", costMoneyOfTotal)for i := 0; i < chouJiangCnt; i++ {rewardId := SelectByWeight(infos)// 组装抽取奖品的数据rewardMap[rewardId] = RewardInfo{RewardId: rewardMap[rewardId].RewardId,RewardName: rewardMap[rewardId].RewardName,Weight: rewardMap[rewardId].Weight,GetCnt: rewardMap[rewardId].GetCnt + 1,}costMoneyOfTotal += costMoneyOfEveryTime// 每次抽奖都有一定间隔,避免运行过快导致一瞬间的随机数种子都一致time.Sleep(time.Nanosecond)}// 以下逻辑按需添加,只是输出指定抽奖结果到文件// 如果 幻神数 > 0,则把抽奖结果记录到文件中,并停止抽奖if rewardMap[1].GetCnt > 0 {jsonRewardMap, _ := json.Marshal(rewardMap)// fmt.Printf("rewardMap: \n%v\n", string(jsonRewardMap))writer := bufio.NewWriter(testJsonFile)writer.WriteString(string(jsonRewardMap) + "\n" + fmt.Sprintf("一共花费RMB:%v", costMoneyOfTotal))writer.Flush()break}}return
}// SelectByWeight 根据不同奖品权重随机抽取奖品并返回
func SelectByWeight(infos []RewardInfo) (RewardId int32) {// 先找出所有奖品数要乘的最大公共倍数 maxMultiplevar maxMultiple int = 1for i := 0; i < len(infos); i++ {strWeight := fmt.Sprintf("%v", infos[i].Weight)// maxMultiple 只关注小数类型权重值,整数类型权重值直接跳过。例如"0.25",我们通过其中的"25"来得到 maxMultiple=100if i := strings.Index(strWeight, "."); i == -1 {continue}var tmpMultiple int = 1for i := 0; i < len(strWeight[i+1:]); i++ {tmpMultiple *= 10}if maxMultiple < tmpMultiple {maxMultiple = tmpMultiple}}// fmt.Println("最大倍数 maxMultiple: ", maxMultiple)// 避免反复扩容,提前预估cap容量:倍率*100// 例如权重为0.85,乘以100倍率后为85;那么对于总权重为100,乘以100倍率后就为1wrewardList := make([]int32, 0, maxMultiple*100)// 根据不同奖品的权重值,分配对应个数的奖品到奖池中for _, info := range infos {for i := 0; i < int(info.Weight*float32(maxMultiple)); i++ { // 填充每个奖品的整数样本个数到容量为1w的奖池中rewardList = append(rewardList, info.RewardId)}}// fmt.Printf("奖池奖品总数 = %v, 奖池奖品明细 = %v \n", len(rewardList), rewardList)// // os.Exit(0)rand.Seed(time.Now().UnixNano()) // UnixNano将t表示为Unix时间,即从时间点January 1, 1970 UTC到时间点t所经过的时间(单位纳秒)return rewardList[rand.Intn(len(rewardList))] // rand.Intn(100)类似于rand.Int()%100
}func main() {// 模拟cf抽奖LotterySimulator(10)
}
运行结果
比如我这次试水了1800,就抽中了概率最低的 幻神−CFS2022\color{red}{幻神-CFS2022}幻神−CFS2022,运气中规中矩。
抽奖结果单独记录到文件:
面试场景题:如何实现带权重值的抽奖程序相关推荐
- POJ 2912 Rochambeau(难,好题,枚举+带权并查集)
下面的是从该网站上copy过来的,稍微改了一点,给出链接:http://hi.baidu.com/nondes/item/26dd0f1a02b1e0ef5f53b1c7 题意:有N个人玩剪刀石头布, ...
- offer收割攻略,Web前端面试真题JavaScript系列(带详解)
临近秋招,不少小伙伴开始了求职之路,面试是十分重要的考验,其次也离不开面试题.网上的面试题零零散散,十分混乱,正好我抽时间帮助大家总结一下.难易程度肯定是从简到难,今天是第一篇的续集--基础篇中的Ja ...
- 2017 西安网络赛A Tree(树上静态查询,带权并查集,矩阵乘法压位,好题)
题目链接 题意: 给出 \(n(n \leq 3000)\) 个结点的一棵树,树上每个结点有一个 \(64 \times 64\) 的 \(0,1\)矩阵,每个结点上的矩阵是根据输入的 \(seed\ ...
- 学习笔记——拓展域并查集和带权并查集
1,拓展域并查集 一般的并查集只能查找出各元素之间是否存在某一种相同的联系,如:a和b是亲戚关系,b和c是亲戚关系,这时就可以查找出a和c也存在亲戚关系.但如果存在多种相对的联系时一般的并查集就不行了 ...
- 视频教程-大牛带你全面剖析Python高频面试真题-Python
大牛带你全面剖析Python高频面试真题 毕业于德国奥格斯堡大学计算机系,先从事分布式网络,搜索引擎等领域的设计开发工作,于2017底混入图灵学院,立志挑战传统培训,做中国最好的Python教育 刘英 ...
- poj1182 and 携程预赛2第一题 带权并查集
题意: 动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形.A吃B, B吃C,C吃A. 现有N个动物,以1-N编号.每个动物都是A,B,C中的一种,但是我们并不知道它到底 ...
- Valentine's Day Round hdu 5176 The Experience of Love [好题 带权并查集 unsigned long long]
传送门 The Experience of Love Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/65536 K (Ja ...
- Java面试之场景题汇总
Java面试之场景题汇总 1. HashMap链表长度大于8后一定会转化为红黑树吗 2. ConcurrentMashMap分段锁之间加了什么锁,读写之间是互斥的吗? 3. 三次握手后如果一直不连接会 ...
- Java面试之场景题(精准到阿里看到都哇哇完)
Java面试之场景题汇总 目录 1. HashMap链表长度大于8后一定会转化为红黑树吗 2. ConcurrentMashMap分段锁之间加了什么锁,读写之间是互斥的吗? 3. 三次握手后如果一直不 ...
最新文章
- linux chpasswd命令 批量修改用户密码
- java 模板引擎_极简 Spring Boot 整合 Thymeleaf 页面模板
- 元旦限时特惠,耳机、书籍等大降价
- 更改Web.config中对上传文件大小限制
- matlab7.x答案,MATLAB习题及参考答案
- logo下方显示技术支持信息_LOGO墙形象设计的几种方案
- VSCode调试Python时终端输出中文乱码解决方法1
- 2020王万生计算机组成原理,计算机组成原理课后习题-王万生(53页)-原创力文档...
- 用OpenCV检测图像中的长方形画布或纸张并提取图像内容
- javascript配置ckfinder的路径
- 遇到 java.io.EOFException 异常的解决办法
- Visual Studio 2013 、Matlab 2014产品密钥
- C语言题库 part.1
- 传说对决服务器无响应怎么办,传说对决一直进不去怎么办
- 如何成功地安装OpenEXR
- html框架页面不允许滚动条,关于html:使用iframe时如何摆脱双滚动条?
- 1.7编程基础字符串11潜伏者
- 博客园博客怎样让百度收录?
- python itchat库_用python【itchat】库获取群聊信息的正确姿势
- 电商数据监测:如何获取想要的电商平台数据?