我说我为什么抽不到SSR,原来是这段代码在作祟...
本文是龚国玮所写,熊哥有所新增修改删减,原文见文末。
我说我为什么抽不到SSR,原来是加权随机算法在作祟
阅读本文需要做好心理准备,建议带着深究到底的决心和毅力进行学习!
灵魂拷问
为什么有 50%
的几率获得金币?
为什么有 40%
的几率获得钻石?
为什么只有 9%
的几率获得装备?
为什么才有 1%
的几率获得极品装备?
是人性的扭曲,还是道德的沦丧,请和我一起走进今日说法 !
介绍
元素被选中的机会并不相等,而是由相对“权重”(或概率)被选中的,是偏心的,这就是加权随机。
举个栗子,假如现在有一个权重数组 w = {1, 2, 4, 8}
,它们代表如下规则。
$ \frac{1}{(1+2+4+8)} = \frac{1}{15} \approx 6.6$ % 几率选中第1个
$ \frac{2}{(1+2+4+8)} = \frac{2}{15} \approx 13.3$ % 几率选中第2个
$ \frac{3}{(1+2+4+8)} = \frac{4}{15} \approx 26.6$ % 几率选中第3个
$ \frac{1}{(1+2+4+8)} = \frac{8}{15} \approx 53.3$ % 几率选中第4个
一个小小的概率问题,居然引出了大大的思考。
方案一、笨笨的办法
所以要设计一个加权算法的程序,你会怎么写呢?
第一个方法把权重所在的位置展开,然后从该列表中随机选择。
假设现在有权重列表 {1, 2, 4, 8}
。
那我们得到的候选列表将是
{0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3}
然后通过 rand.Intn() ,获取一个随机数,就完成了,代码如下。
func weightedRandomS1(weights []int) int {if len(weights) == 0 {return 0}var indexList []intfor i, weight := range weights {cnt := 0for weight > cnt {indexList = append(indexList, i)cnt++}}rand.Seed(time.Now().UnixNano())return indexList[rand.Intn(len(indexList))]
}
当权重特别大的时候,这种方案费时费力,又占空间。先别急往下看,你能想到更好的办法吗?
方案二、略显聪明
由于总权重为 15(1+2+4+8)
,我们可以生成一个 [0,15)
的随机整数,然后根据这个数字返回索引。代码如下。
func weightedRandomS2() int {rand.Seed(time.Now().UnixNano())r := rand.Intn(15)if r <= 1 {return 0} else if 1 < r && r <= 3 {return 1} else if 3 < r && r <= 7 {return 2} else {return 3}
}
妙不妙!!但你以为这就是效率最高的办法吗?
写那么多if else
不痛苦吗我的宝贝。
方案三、神之一手
何必将随机数和所有的范围进行比较呢?直接遍历随机数减去权重,如果结果小于等于零,不就是我们要的结果下标吗?
func weightedRandomS3(weights []int) int {rand.Seed(time.Now().UnixNano())r := rand.Intn(len(weights))for i, v := range weights {r = r - vif r <= 0 {return i}}return len(weights) - 1
}
这种方法叫放弃临时名单。想不明白就评论问!
方案四、小小优化
对于方案三,怎么有效的减少遍历次数呢?
当r
小于等于 0
的速度越快,算法越高效。那我们就让 r
到达 0
更快。先排序这样就能先减去权重大的,减少遍历次数。
func weightedRandomS4(weights []int) int {sort.Sort(sort.Reverse(sort.IntSlice(weights)))....
有人就不服了,排序不是更浪费时间?
是的!虽然看起来减少遍历次数!但排序本身就要遍历就是更浪费时间。。。
但是一次排序,反复使用,还是能提高效率的!
方案五、不可思议!
有没有办法不用排序,而让原数组有序呢?
有人就说了,你这不是扯么?
如果每次遍历都加上上一个权重,那整个数字就是递增的!
再用二分就能加快速度了,时间复杂度从 $ O(n) $ 直接变为 $ O(log(n)) $。
func weightedRandomS5(weights []int) int {rand.Seed(time.Now().UnixNano())sum := 0var sumWeight []intfor _, v := range weights {sum += vsumWeight = append(sumWeight, sum)}r := rand.Intn(sum)idx := sort.SearchInts(sumWeight, r)return weights[idx]
}
读到这里,对源码没有信心学习的朋友,可以直接撤了。 真正的高阶优化要来了。
方案六、不死不休
到目前的位置,我们的解决方案已经足够好了,但是仍然有改进的余地。
方案五中,我们使用了 Go
标准库的二分查找算法 sort.SearchInts()
,是封装了通用的 sort.Search()
函数,如下。
sort.Search()
的函数参数需要一个闭包函数,并且这个闭包函数是在 for
循环中使用的,如下。
闭包函数反复调用,在编译期会产生额外的开销。因为会产生更多的跳转,跳转会引起压栈(函数参数都是会压栈的)。
我们手动提出取函数,就可以减少编译器的内联(文末会解释)。
func weightedRandomS5(weights []int) int {
...
idx := sort.SearchInts(sumWeight, r)return weights[idx]
}
func searchInts(a []int, x int) int {i, j := 0, len(a)for i < j {h := int(uint(i+j) >> 1)if a[h] < x {i = h + 1} else {j = h}}return i
}
通过基准测试可以看到吞吐量提升了 33% 以上。对于大型数据集,优势越明显。
方案七,“偷鸡”取巧--轮盘赌
目前为止我们所有的方案都有一个共同点 —— 生成一个介于 0 和“权重之和”之间的随机数,并找出它属于哪个“切片”。
还有一种不同的方法。
func weightedRandomS7(weights []float64) int {var sum float64var winner intrand.Seed(time.Now().UnixNano())for i, v := range weights {sum += vf := rand.Float64()if f*sum < v {winner = i}}return winner
}
- 从命中的角度去考虑。
- 权重越大,命中率自然越大了。
- 既然是随机,多次随机和单次随机而言都是随机的。
这个算法的一个有趣的特性是你不需要提前知道权重的数量就可以使用它。所以说,它或许可以用于某种流。
尽管这种方案很酷,但它比其他方案慢得多。相对于方案一,它也快了 25%
。
小结
- 下标直接展开到列表里,随机长度取值。
if else
取值。- 遍历随机数减去权重,结果小于等于零时。
- 先排序,再用方法三。
- 免排序,直接加和,再二分。
- 优化源码中的二分法。
- 轮盘赌算法,每次都去赌。
内联:编译器的一个名词。我们的代码最终都是经过编译系统转换成可执行二进制文件。汇编阶段读取的是词法、语法单元输出的结果。而内联是编译器对词法、语法分析器对源代码做出的分析,然后产生二进制代码这个过程叫内联。
源代码
https://github.com/guowei-gong/weighted-random
原文:加权随机设计与实现
一起进步
你好,我是小熊,是一个爱技术但是更爱钱的程序员。上进且佛系自律的人。喜欢发小秘密/臭屁又爱炫耀。
奋斗的大学,激情的现在。赚了钱买了房,写了书出了名。当过面试官,带过徒弟搬过转。
大厂外来务工人员。是我,我是小熊,是不一样的烟火欢迎围观。
我的博客 机智的程序员小熊 欢迎收藏
相关文章:

我说我为什么抽不到SSR,原来是这段代码在作祟...
本文是龚国玮所写,熊哥有所新增修改删减,原文见文末。 我说我为什么抽不到SSR,原来是加权随机算法在作祟 阅读本文需要做好心理准备,建议带着深究到底的决心和毅力进行学习! 灵魂拷问 为什么有 50% 的几率获得金币&a…...
MySQL MGR 集群新增节点
前言 服务器规划现状(CentOS7.x) IP地址主机名部署角色192.168.x.101mysql01mysql192.168.x.102mysql02mysql192.168.x.103mysql03mysql192.168.x.104proxysql01proxysql、keepalived192.168.x.105proxysql02proxysql、keepalived 新增服务器IP&#x…...

【单目标优化算法】蜣螂优化算法(Dung beetle optimizer,DBO)(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...

【C++】类和对象入门必知
面向过程和面向对象的初步认识类的引入类的定义类的访问限定符封装类的作用域类的实例化类对象模型this指针C语言和C实现Stack的对比面向过程和面向对象的初步认识 C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解…...
day38 动态规划 | 509、斐波那契数 70、爬楼梯 746、使用最小花费爬楼梯
题目 509、斐波那契数 斐波那契数,通常用 F(n) 表示,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是: F(0) 0,F(1) 1 F(n) F(n - 1) F(n - 2),其…...
2023年备考软考必须知道的6件事
不知不觉,距离2023年上半年软考也只有不到100天的时间了,报名入口也将在3月13日正式开通,你是正在犹豫是否参加考试? 还是已经开始着手准备复习? 关于软考考试你还有哪些疑问? 2023年备考软考必须知道的6件事,建议收藏…...

GLOG如何控制输出的小数点位数
1 问题 在小白的蹩脚翻译演绎型博文《GLOG从入门到入门》中,有位热心读者提问说:在保存日志时,浮点型变量的小数位数如何设置? 首先感谢这位“嘻嘻哈哈的地球人”赏光阅读了小白这不太通顺的博客文章,并提出了一个很…...

2022年全国职业院校技能大赛(中职组)网络安全竞赛试题A(6)
目录 模块A 基础设施设置与安全加固 一、项目和任务描述: 二、服务器环境说明 三、具体任务(每个任务得分以电子答题卡为准) A-1任务一:登录安全加固(Windows) 1.密码策略 a.密码策略必须同时满足大小…...

Safety-Gym环境配置与安
官网: https://github.com/openai/safety-gym https://github.com/openai/safety-starter-agents 一、安装依赖环境配置 建议使用python 3.7及以下环境,因为官方的safety-rl是基于tensorflow1.13.1实现,而tensorflow1.13.1只能支持python…...

3月再不跳槽,就晚了
从时间节点上来看,3月、4月是每年跳槽的黄金季! 以 BAT 为代表的互联网大厂,无论是薪资待遇、还是平台和福利,都一直是求职者眼中的香饽饽,“大厂经历” 在国内就业环境中无异于一块金子招牌。在这金三银四的时间里&a…...

HTTP cookie格式与约束
cookie是前端编程当中经常要使用到的概念,我们可以使用cookie利用浏览器来存放用户的状态信息保存用户做了一些什么事情。session是服务器端维护的状态。session又是如何和cookie关联起来。后面介绍cookie和session的使用。Cookie 是什么?RFC6265, HTTP …...

docker基础
docker基础 docker概述 docker的出现?docker解决思想docker历史docker链接docker能干什么?开发-运维 docker安装 镜像(image)容器(container)仓库(repository)底层原理 docker命令 帮助命令镜像命令 docker-images查看所有本地主机上的镜像docker-searc…...

【微信小程序】--JSON 配置文件作用(三)
💌 所属专栏:【微信小程序开发教程】 😀 作 者:我是夜阑的狗🐶 🚀 个人简介:一个正在努力学技术的CV工程师,专注基础和实战分享 ,欢迎咨询! &#…...
EDA-课设
EDA-课程设计-电子闹钟 一、实验目的 1.掌握多层电路在 QuartusII 集成开发环境中的实现; 2.熟练掌握基于 QuartusII 集成开发环境的组合逻辑电路设计流程; 3.掌握基于 QuartusII 集成开发环境的时序逻辑电路设计流程; 4.理解有限状态机设计…...

C/C++每日一练(20230222)
目录 1. 部分复制字符串(★) 2. 按字典顺序排列问题(★★) 3. 地下城游戏(★★★) 附录 动态规划 1. 部分复制字符串 将字符串2小写字母复制到字符串1:编写程序,输入字符串s2,将其中所有小写字母复制到字符串数组strl中。例如:aal1bb22cc33de4AA55…...

Java API 文档搜索引擎
1. 认识搜索引擎:在搜狗搜索的搜索结果页中, 包含了若干条结果, 每一个结果包含了图标, 标题, 描述, 展示URL等搜索引擎的本质:输入一个查询词, 得到若干个搜索结果, 每个搜索结果包含了标题, 描述, 展示URL和点击URL2. 搜索引擎思路:2.1 搜索的核心思路:当前我们有很多的网页(…...

2023美赛C题Wordle二三问分布预测和难度分类预测
文章目录前言题目介绍人数分布预测首先建立字母词典,加上时间特征数据预处理训练和预测函数保存模型函数位置编码模型及其参数设置模型训练以及训练曲线可视化预测人数分布难度分类预测总结前言 2023美赛选了C题,应该很多人会选,一看就好做&…...

gdb的简单练习
题目来自《ctf安全竞赛入门》1.用vim写代码vim gdb.c#include "stdio.h" #include "stdlib.h" void main() {int i 100;int j 101;if (i j){printf("bingooooooooo.");system("/bin/sh");}elseprintf("error............&quo…...
如何使用python AI快速比对两张人脸图像?
本篇文章的代码块的实现主要是为了能够快速的通过python第三方非标准库对比出两张人脸是否一样。 实现过程比较简单,但是第三方python依赖的安装过程较为曲折,下面是通过实践对比总结出来的能够支持的几个版本,避免大家踩坑。 python版本&a…...
(2)C#传智:变量基础(第二天)
一、注释符 不写注释是流氓,名字瞎起是扯蛋。 注释作用:解释与注销 命名: 以字母、_、开头,里面只能有_与特殊符,其它不得出现如%*&^等。 不能与关键字重复。区分大小写,Num…...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...

RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...
使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度
文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...
在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案
这个问题我看其他博主也写了,要么要会员、要么写的乱七八糟。这里我整理一下,把问题说清楚并且给出代码,拿去用就行,照着葫芦画瓢。 问题 在继承QWebEngineView后,重写mousePressEvent或event函数无法捕获鼠标按下事…...

RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...

MySQL:分区的基本使用
目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区(Partitioning)是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分(分区)可以独立存储、管理和优化,…...

uniapp 小程序 学习(一)
利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 :开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置,将微信开发者工具放入到Hbuilder中, 打开后出现 如下 bug 解…...
【Elasticsearch】Elasticsearch 在大数据生态圈的地位 实践经验
Elasticsearch 在大数据生态圈的地位 & 实践经验 1.Elasticsearch 的优势1.1 Elasticsearch 解决的核心问题1.1.1 传统方案的短板1.1.2 Elasticsearch 的解决方案 1.2 与大数据组件的对比优势1.3 关键优势技术支撑1.4 Elasticsearch 的竞品1.4.1 全文搜索领域1.4.2 日志分析…...