
揭秘AI的另一面:那些不为人知却改变数百万人生活的应用场景
当我们谈论人工智能(AI)时,常常关注其在硅谷的研究实验室或公司总部的应用。然而,AI在其他领域的影响力不容小觑,尤其是那些直接影响到数百万人日常生活的应用场景。 预测自然灾害 人工智能在自然灾害预测中展现出了强大的能力。例如,谷歌DeepMind的GraphCast和华为的盘古气象等模型能够在几分钟内准确预测天气和灾害,相比传统的物理模型,其精度和速度更高。这些技术在灾难响应中至关重要,能够帮助救援人员提前准备应对。 法律行业的AI革命 AI技术在法律行业中的应用已经开始改变传统的工作方式。根据汤森路透的报告,77%的律师认为,人工智能将在未来五年内对他们的领域产生重大影响。AI帮助律师们节省大量时间,用于更具创造性的工作。 教育与包容性 AI的应用还体现在教育和包容性领域,特别是在为有残障的人群提供更多机会方面。例如,Ava公司开发的AI应用程序能够为听力障碍人士提供实时转录服务,极大地改善了他们的沟通能力。 AI的真正价值 虽然AI的很多应用可能没有自动驾驶或聊天机器人那样引人注目,但它们对社会的影响却更加深远。AI技术能够帮助解决一些紧迫的人类问题,例如提升灾难响应的效率、促进法律公平和帮助残障人士更好地融入社会。这些应用展现了AI的真正潜力,即作为解决社会问题的重要工具。

AI玩具,狂飙下的另一面
AI玩具,狂飙下的另一面 AI陪伴硬件热潮涌现,多品牌跨界布局,成熟产品仍待突破。 «每个新品好像都是一个AI陪伴,又好像并不算是。 主打AI玩具、AI陪伴等概念的硬件产品,火热依然。 在近一个多月的部分公开信息中,有多款AI陪伴类新品陆续官宣,其背后企业除了玩具、教育品牌外,也有一些跨界选手。 智能电动毛绒品牌趣巢近日推出AI抱抱喵。 "Image 10" (https://wm-ai.oss-cn-shanghai.aliyuncs.com/test/v2_2cfe13e2a3a842b587d3565ec6695124@000000_oswg120953oswg1080oswg1046_img_000.jpg) 官方对它的定义是“AI智能喵型机器人”,“多感官多模态AI陪伴毛绒玩具”。据介绍,抱抱喵是趣巢旗下的经典玩具形象,一代产品全网销量突破50万,AI抱抱喵是其二代产品。 形象上,AI抱抱喵延续了一代的造型,但尾巴、眼睛等细节上有一些差异化设计。 功能上,AI抱抱喵基于AI技术,更加真实地模拟真实猫咪的行为和反应,包括如发出呼噜声,摆动尾巴,通过身体语言回应用户的抚摸等等。 "Image 11" (https://wm-ai.oss-cn-shanghai.aliyuncs.com/test/v2_d7a8e123d7da4176a2e67f4d80587566@000000_oswg154568oswg1080oswg1514_img_000.jpg) 另外,基于AI对话系统,AI抱抱喵会有一定的情感分析功能,再以小猫的形象去给出回应,比如用户发出对话:“我今天超开心”时,AI抱抱喵会通过眨眼睛、摇尾巴、发出开心的猫叫声等方式进行互动。 豪威科技最近推出AI儿童陪伴机器人“源宝”。 源宝是豪威科技集团依托于哈工大机器人研究所和鹏城AI实验室技术,联合南师大、北京大学心理与认知科学学院、阿里巴巴、字节跳动、科大讯飞等二十多家合作伙伴,共同开发的AI机器人。 "Image 12" (https://wm-ai.oss-cn-shanghai.aliyuncs.com/test/v2_128282a0f93a4538b0b6d34b37cebc14@000000_oswg942519oswg1080oswg608_img_000.jpg) 除了百科知识、日常对话、成长记录外,它比较差异化的地方是心理部分的设计。目前其嵌入30+心理学儿童评估模型;涉及实时监测情绪波动+行为分析,危险行为预警提醒直达家长手机的功能;另有20款心理引导模型,正念引导、压力释放引导、冥想引导等等。 "Image 13" (https://wm-ai.oss-cn-shanghai.aliyuncs.com/test/v2_91f234515b2d4a12989c57c85c438198@000000_oswg920996oswg1080oswg810_img_000.jpg) 集贤科技近日官宣了灵珠灵语AI玩具创新计划。 “灵语灵珠”计划的核心,在于三方的能力聚焦。技术上,通过集贤科技的智能硬件设计能力、博通集成的物联网芯片技术,以及火山引擎大模型能力的AI大模型技术进行合作,推出兼具灵敏、智能和AI性的AI玩具…… "Image 14" (https://wm-ai.oss-cn-shanghai.aliyuncs.com/test/v2_bcb3b18ce577471a9d05b7307d7ecd07@000000_oswg670219oswg1080oswg575_img_000.jpg) 不过目前还没有看到具体的AI玩具形态。 乐开科技近日推出AI互动玩偶。 乐开科技通过自研AI AGENT芯片模组植入大语言模型,用“安全芯核+情感赋能”的技术架构推出相应的解决方案,其已应用于AI互动玩偶和AI智能锁中。 "Image 15" (https://wm-ai.oss-cn-shanghai.aliyuncs.com/test/v2_80349884cfba4dd48b86d130b7236127@000000_oswg80204oswg1080oswg608_img_000.jpg) 目前,乐开科技的AI互动玩偶提供对话互动、游戏互动、百科问答、情绪报告、成长日记等服务,同时其还搭载定位芯片和4G+WIFI双通讯功能,可以实现亲子联系。 "Image 16" (https://wm-ai.oss-cn-shanghai.aliyuncs.com/test/v2_60a0cc70a09a42bfa43080fd4e7bbcfd@000000_oswg71410oswg1080oswg608_img_000.jpg) 实际上,正有越来越多的公司,加入到AI玩具的解决方案之中。 **此前,FoloToy和小智AI算是大家比较熟悉的解决方案品牌。**其中前者曾与字节跳动合作推出AI玩具“显眼包”,后者则是把AI玩出了更多可能性。随着AI玩具、AI陪伴产品的发展,越来越多公司加入到解决方案的赛道上。 声网在中国家电及消费电子博览会(AWE2025)上推出对话式AI开发套件,该套件可以满足多种场景,如AI玩具、智能硬件、家居语音助手等应用场景。 "Image 17" (https://wm-ai.oss-cn-shanghai.aliyuncs.com/test/v2_0870ef410c5843ae930fa3c45b7e1dbd@000000_oswg1045228oswg832oswg646_img_000.jpg)»

面试的一些想法和技巧
写在前面 最近一直在面试,之前 gap 过 4 个月,一直在找工作/面试,也算积攒了一下经验,现在分享给大家。 发之前想了很多防杠的提前说明,但是又想了一下,无所谓啦。发出来主要是希望能帮到大家,至于会不会被喷/杠无所谓啦。 面试前 简历编写 最好按照时间从近到远记录项目和公司,确保面试官能看到你最近做的项目。 不要写公司主体,尽量写公司最知名的名字。例如你写上海拉扎斯,谁知道这是饿了么呢? 多写一些关键词,很多 hr 筛选简历的时候基本上都是根据关键词进行匹配的,多写关键词有利于你通过初筛。 写清楚是全日制本科学信网可查,简历上留好电话和微信邮箱等联系方式。很多公司的招聘系统都需要通过邮件发送面试邀约。 项目中可以运用所谓的STAR法则,写清楚项目的背景、难点、解决方案和取得的成果,当然如果有一些部分实在不好写就不写了。 简历优化 个人不建议简历胡编乱造,一个谎言往往要用更多的谎言来弥补,你总有露馅的时候。 但是建议可以适当美化一下,多发掘项目优点,多写自己的贡献。 例如服务并发不高就写自己在代码里面的优雅设计模式,如果代码写的稀烂就写用的什么架构能承担多少 QPS ,避弱就强,多写自己的闪光点,这不算撒谎。 至于简历到底是写多了好还是写好了少,仁者见仁智者见智,我更喜欢的是写的简洁一些,不用花里胡哨的东西,最好做成 pdf ,兼容性更好一些,因为你永远不知道面试过用的 office 还是 wps 。 注意写在简历里面的内容一定要能答得上来,面试官不是傻子,也讨厌别人拿他当傻子 ps:建议面试前查一下公司主营业务,如果面试官问起来可以说了解过贵公司/部门的业务,这样可以争取一定的好感。 简历投递 目前来看,互联网的招聘,主要有平台投递,官网投递,内推投递三种。 内推 这绝对是性价比最高的投递方式,对于用人单位来说来自内部推荐的简历一般会更靠谱,并且效率也会更高一些。个人认为 1 内推=30 海投。 官网投递 一般的大厂都有自己的招聘平台,上面也会有一些岗位,但是按照我个人的经验来看,一般只有大规模校招的时候,hr 团队才可能会看一眼官网投递这个渠道,其他的时候官网投递很容易被 pass 掉,如果你是应届生可以考虑一下这个渠道。 平台招聘 现在比较靠谱的应该只有 BOSS 直聘了,至于拉钩、猎聘之类的基本没有 hr 在用了,当然如果是找外企可以看看领英等等平台。 关于海投 海投是最简单成本最低的方式,但是效率也是最低的。在海投的时候,建议多关注一下 jd 和公司的基础信息,以提高投递效率。 首先,人数 0-99 是的小公司是不建议去的,相信我绝对很坑,不存在小而美。 其次,jd 写的取巧/搞笑/标题党的不要去。 然后,公司名称和上班地点不符的,名字里带人力“人力”、“信息”的小公司或者简介里面有类似的,0-99 人的公司,基本都是外包。 最后,是否选择和猎头合作,见仁见智,反正我从来没在猎头那得到过一个面试机会。 约面 如果面试比较多,建议用各种日程管理工具记录好面试时间和公司名称,最好不要错过面试。 面试中 准备好网络和一个房间,不要让家人出现在摄像头里,提前几分钟进入面试间调试好设备。如果是线下面试最好带一份自己的简历。 自我介绍 一定会有这个问题!!!所以一定要提前准备好!!!!!!!!! 这个自我介绍的作用,一个是面试官需要趁机扫一眼你的简历,另外一个就是需要大概了解一下你的情况。 所以,建议提前准备好自我介绍的内容,主要讲清楚,你是谁?你会啥?几年经验?做过什么业务等等? 这里有一个模板仅供各位参考一下。 «面试官你好,很高兴参见今天这次(对方公司名或者岗位名)的面试。我叫 xxx ,毕业于 yyy 大学 zzz 专业,xx 学历,有 n 年工作经验,曾经做过 xxx ,yyy ,zzz 等业务。 接下来我简单介绍一下我最近工作过的 x 家公司/做过的 n 个项目» «(项目介绍)讲清楚做的什么业务,这个业务是啥?你主要负责什么?» «以上就是我最近情况的一个简介,更多内容都在我的简历里面,谢谢。» 自我介绍也可以多说细节,也可以粗略介绍,不同的面试官有不同的喜好,但是建议多说一些。 最主要是多浪费点时间,一般面试都是固定 1 小时,面试官也有别的安排,个人介绍多一点时间,那八股文和算法拷打部分就容易少一些。如果被面试官打断,就要赶紧切换成简单介绍。 如果你是应届生,主要的介绍要说清楚为了胜任这个岗位,你做了什么。例如面云开发,你可以说自己学习了容器技术,k8s ,网格服务等等。 离职/gap 原因 一定会问!!! 自己提前想好。虽然面试官什么都知道,你也知道面试官什么都知道,面试官也知道你知道他知道...(无限套娃)但是这个世界就是这么癫。 所以我也准备了一个模板。 «上家公司把我调到 xx 岗,该岗位与我自己的职业规划有很大偏差,我希望做的是 yy 方向。(这个 yy 最好是你面试的这个岗位的方向)» «由于身体不好/身体哪里受伤所以修养了一段时间,或者家人哪里需要帮忙等等,算是问到 gap 的时候不会太踩雷的一个答案。» 八股文 必然会问,只是多少的问题。包括但不限于操作系统、网络、数据库、语言等等,根据面试岗位不同而不同。 虽然你知道八股文没啥用,面试官也知道没啥用,你也知道(又开始套娃)...但是一定会问。 这部分除了平时多积累没有别的办法,建议可以多刷脉脉社区或者 B 站的面试视频等等,一定要确保常见/基础的八股文没有问题。 需要注意,会就是会,不会就是不会,不要瞎扯淡。如果问了某个东西不会,可以尝试引导一下话题到你熟悉的内容。 例如可以说:xxx 我确实不太清楚,如果我是设计者我可能会 yyy ,不过对于 zzz 技术我比较了解,您感兴趣的话咱也可以聊聊。 算法题 算法题一般大厂必考,中小厂会选择性的考。 有时候也不是纯算法题,而是会有设计题,例如设计一个 lru 等等。 建议看 b 站 up 主灵茶山艾府的视频,该巨佬视频分为基础算法题和周赛两个类别,视频内容简洁,教学清晰,干货比撒哈拉大沙漠都干。解题思路如德芙巧克力般顺滑,编码实现如庖丁解牛般顺畅。实在是顶礼膜拜,除了牛逼二字再也找不到任何的形容词。 我在学习了这位巨佬的基础算法视频后,在去年 gap 的 4 个月里面了不下 50 次(多为大厂),基本算法通过率在 50%以上。( ps:本人之前真的很菜) 如果只是为了应对一般面试建议看完基础算法系列即可,如果想冲击字节等头部大厂,建议刷完他的全部视频,那应对所谓的算法题如探囊取物。 一般小厂面算法以比较简单/经典的题目为主,主要考察的是编码的熟练程度,例如二分查找/冒泡排序等等。 并且由于很多中小厂买不起在线面试 coding 平台,所以一般会让你共享屏幕开 ide 写,这样有 ide 辅助大家应该会好写一些。并且由于没有很多 case 所以即使写的有点问题大部分情况下也能通过。 大厂一般都是在线编码平台和题库,主要考察算法功底和编码水平。面试任何大厂建议一定要最起码做完并且做熟力扣最常见 150 题。并且建议不要在 hard 水平/2000 序列后的算法题花太多时间,因为性价比不高。 很多大厂,做完算法题一样会挂你。真想要你算法题不会为难你,不想要你一个手写红黑树就把你淘汰了。 在高难度和排序靠后的算法题上倾注太多时间是性价比很低的行为( ps:如果你是字节出来的,一定刷一下接雨水/手动 doge )。 如果是设计类算法题,无他耳唯手熟矣,多刷多背吧。 系统设计题 一般这部分很少问,也都是开放题,没有什么标准答案。但是针对一些常见的系统设计,也可以准备好一个设计方案。 例如: 如何设计一个秒杀系统 如何设计一个短链系统 如果设计一个 ab 实验平台 如何 balabala 这种题目主要考察的是你的全局观和细节能否关注到,一般核心考察的还是并发问题。 面试中,不需要你答对所有题目,因为公司要的不是厉害的人,而是便宜且合适的人。所以在这个时代你需要做个价格低的,而不是技术牛的。 你的预期薪资是多少 如果是 hr 问一般是想了解一下你预期的期间范围,看跟公司的招聘预算是否匹配,因为预算是定死的,一毛钱也不会多给,早一点匹配可以快速筛选出合适的候选人。 如果是一面/二面面试官询问,也可能就是想了解一下行情,方便自己跑路的时候要价? 什么时候能入职? 建议如实说出你的想法就行,有特殊情况的提前沟通。 还有什么想问我的 这是最关键的一个环节,很多人都会忽视什么都不问,这是你能了解这个公司/岗位最好的机会。 建议问清楚以下几个问题: - 这个岗位主要负责什么内容?具体做啥? - 这个岗位所在的组有多少人?总技术人员有多少人? - 面试有几轮?分别是什么角色作为面试官? - 内部协作方式?业务上是专人转岗还是交叉负责? - 其他的,你在意的技术上的点 唯独不建议问面试中没答上来的题目,因为这样会给面试官一个你“不服气”的错觉。 问 hr (可以时候微信询问): - 公积金系数和比例 - 年终奖和绩效考核周期/标准/等级 - 附近租房和外卖是否方便 - 发什么电脑?有没有电脑补助? - 期权/股票?有的话,分几次几年比例? - 加班费/年假/其他福利等等 ps:在这里告诉一个小技巧:不要一下子问 hr 很多问题,因为对方很容易故意不回答某个问题,你可能也不好意思再问一次。所以要一个问题一个问题的发问,这样对方想继续话题就必须回答上面的问题。 关于外包的话题 有些老哥或许没有很高的学历,也没有很好的背景,或者什么都有但是依然拿不到 offer ,考虑去外包不是一件什么丢人的事,都是为了生活。 但是首先,外包并不意味着轻松,虽然有些岗位确实轻松,但是从我混迹社区的经验来说,外包不仅仅是工资底,也完全没有任何地位(没有歧视老哥们的意思),毕竟"外包同学不要偷吃零食"的梗不是空穴来风。 如果非要去外包,还是建议去一些大厂的外包,例如华为 od ,字节外包等等,基建水平会相对完善一下,办公环境也会好很多,薪资也比一般的外包高不少。 其次,现在的环境不用我说也都知道,就算是外包也要求本科起步学信网可查,所以也可以提前准备好学信网截图。 至于专科的老哥,建议仔细考虑一下转行的可行性。(并非歧视,现实确实很难,就我刷 boss 的经历来说很多外包都找过我,要求清一色是统招全日制本科+学信网截图) 拿到 offer 到这就恭喜你上岸了,虽然可能是从一个坑到另一个坑,但是整个世界都是个草台班子,不用太在意。 如果你有多个 offer 拿不准,最好先跟家人商量,再跟网友商量。因为网友不知道你的情况没有上下文,分析的内容可能是不对的或者不适合你。 *** 感谢你读到最后,这篇帖子是我在其他平台看到mark到的,希望对求职找工作的你有所帮助。 *** 如果还没找到工作,快过年了,开开心心给自己放个假吧!

大家面试别人流程是怎么样的
5年后端,懂些前端,在微小公司,基本都是面 1-3 年的人 面试别人的话,套路差不多是固定的 看项目 看项目客户端,再问下实现细节。 假设有个聊天功能,我会问: 用啥语言 用啥函数/扩展包 用啥协议 用啥存储 用啥保证心跳 生产用啥部署的。 (回答/乱答不出来,基本是培训机构) 用代码问代码理解 比如闭包和 promise 数组方法。 闭包: a.forEach(item => console.log( item() )) a = # a 等于什么可以打印 helloworld promise (难点的 可以加个定时器): a = () => Promise.resolve(1) b = () => Promise.resolve(2) c = () => Promise.resolve(3) 根据 a b c 打印 1 2 3 数组: [].map [].reduce [].filter (真一堆人只知道 for)

面试时怎么回答业务亮点和难点?
作为一个应用商店类型的后端开发,面试时经常被问你们的业务难点在哪,or 技术难点在哪,想破头皮都想不出来,一是怕说出来的东西被面试官认为就这有什么好说的,二是确实工作中能解决的都不算难点。说一些性能优化或者代码重构,解决了什么问题,这就是我想出来的东西。但是最后的面评经常被写业务复杂度不高,业务不匹配。 大家都是怎么回答这个问题的,或者阿里字节美团等大厂同学你们是怎么准备这个问题,or想从候选人那听到什么

面试开启AI“外挂”,真能更快拿下Offer?
金三银四的求职季如约而至,求职者常常面临面试这一难关。随着人工智能技术的飞速发展,一些专为求职者量身定制的AI面试助手也进入了市场,它们声称可以帮助求职者熟悉面试流程,预测问题,甚至给出回答建议。记者亲自体验了几款主流AI面试助手,分别是智面星、多面鹅和Offerin AI,它们各自具有不同的特点。 智面星:答案刻板,报告“真香” 智面星的面试助手具备较强的岗位匹配功能,通过填写意向岗位后,系统自动生成相关岗位需求。在模拟面试过程中,面试助手提供标准化的问题及回答建议,但这些问题和答案较为刻板,未根据求职者的实际表现作出调整。面试结束后,系统会生成一份详细的模拟面试报告,帮助求职者了解自己在面试中的表现,指出优缺点,并提出改进建议。 "智面星模拟面试" (https://img.36krcdn.com/hsossms/20250306/v2_669f62f82bc24affb4341c079abcf47e@000000_oswg54125oswg1080oswg803_img_000?x-oss-process=image/format,jpg/interlace,1) 多面鹅:模拟多样化面试官风格 多面鹅的界面设计简洁,提供了更多的个性化选择,如面试官性格、问题偏好和答案风格。AI面试官能根据求职者的回答做出调整,模拟更真实的面试过程。尽管App端存在闪退问题,但电脑端相对稳定。面试后生成的报告相对较为详细,还提供面试官与求职者的契合度分析。 "多面鹅模拟面试" (https://img.36krcdn.com/hsossms/20250306/v2_64e03bbf216a4477bc4ebc396e5ee207@000000_oswg82178oswg1080oswg628_img_000?x-oss-process=image/format,jpg/interlace,1) Offerin AI:隐蔽性有“高招” Offerin AI提供多种面试模式,包括面试、笔试、AI聊天及简历模式。特别适用于技术类岗位,如程序员和数据分析师等,并注重隐私保护。用户可以选择不同的隐蔽模式以保持面试的隐私性。它的技术能力也包括编码题目的解答,极大地帮助了技术类岗位的求职者。 "Offerin AI面试界面" (https://img.36krcdn.com/hsossms/20250306/v2_d76918b6f80f4b4ea51ce639847fe759@000000_oswg82397oswg1080oswg604_img_000?x-oss-process=image/format,jpg/interlace,1) 记者实测:AI面试“外挂”风险需谨慎 虽然这些AI面试助手能帮助求职者准备面试,提升自信,但许多功能都需要付费解锁,试用版的功能限制了整体体验。在使用之前,求职者应明确自己的岗位目标,准确填写个人信息,以便AI系统生成最相关的面试问题。

超全!面试问题➕技巧➕话术
很难有这么全的了:面试问题➕技巧➕话术 面试结束后,大家都想知道面试结果。下面是几个面试成功的预兆,可做个参考! ✔️主动谈薪资 面试官主动提出薪酬问题,面试通过率90%。大家最关心的就是自己的待遇问题了,不过这个问题由自己提出来的效果就要比面试官主动提出来的效果差很多。当面试官在跟我们讨价还价时,意味着我们的知识和技能是能够跟岗位职责和要求相匹配的,薪酬能否谈好,也是决定录用的一个因素。 ✔️主动的介绍工作 主动提及工作安排以及大概工作内容要求,成功率在60%以上。当在面试的过程中,面试官频繁的问及你曾经的工作经验,并 且对某一段特定的时间, 更是要求你详细表达的时候,就说明他有兴趣,如果你在这个时候,能够把握机会,恰到好处地表达出自己的优点,那么你成功的几率就已经可以达到60%这个及格线了。 ✔️疯狂的输出福利 当面试官在常规面试问题结束之后,开始给你安利公司的各种福利以及团哪队工作氛围等等,说明面试官或许已经开始争取你了。但是如果只是常规的介绍,就不要多想了。 ✔️主动加你的联系方式 当面试官(尤其是主管类)主动要加你的联系方式,且提到后续有任何问题可以随时跟他沟通,基本上就是他锁定你了! ✔️面试时间长短 一般来说,大家面试时间普遍在20min-40min之间,如果超过了45min,说明对你非常感兴趣。 ✔️面试结束后让你等一下 面试结束后没有让你直接走,而是让你稍坐一下,这种情况基本上是请更高级的领导要不要复试或者直接确定你入职。 ✔️面试官表现出尊重你 面试官注重透露岗位的一些细节,没有对你压力面试、没有玩手机、不专注、小动作频繁、发呆。

面试几个都挺友善
面试了几个暂时没有收到offer 市场确实有点悲观情绪

面试官让你自我介绍的时候,他到底想听到什么?
被面试了很多次了,也面试过别人很多次 还是不太明白面试官开头为什么还要让人自我介绍 明明简历上自己的信息已经写清楚了基本信息、教育经历、工作经历、甚至还有工作内容

年底了,还有人面试找工作吗?
2025年元旦还有一周,2025年春节还有一个月,还有人在找工作吗?是不是都放寒假啦? "https://wmprod.oss-cn-shanghai.aliyuncs.com/community/FjtSGxiBsaNxJ4byW6dhsqGbVeyt.png" (https://wmprod.oss-cn-shanghai.aliyuncs.com/community/FjtSGxiBsaNxJ4byW6dhsqGbVeyt.png)

1.面试为什么会考概率
这里我们将开启新的一个专题:概率题面试突击。为什么想写这个专题呢,主要是因为最近几年在程序员面试中,尤其是校招面试中,经常会问到一些与概率、期望相关的问题,一方面是考查候选人的数学素养,另一方面是概率在计算机中的应用确实很多。这些题零零散散出现在各个平台的面经,周围同学朋友的口述,以及自己的面试实践中,没有一个系统的梳理,因此本专栏的目的就是详细梳理程序员面试中常见的知识点与题目。当然,本专栏不只是适合程序员。算法工程师、策略产品经理,数据分析师都是最近几年新出现并且比较热门的岗位,而概率在这些岗位中是通用基础,面试中基本上都会问到。此外,很多程序员会去金融科技工作,而金融业中的技术岗位,概率是必考的,并且会重点考。准备这些岗位的同学,有时间的话都可以学习本专栏,将来一定会有用。我工作至今的职位一直都是算法工程师,这里我从算法工程师的角度谈一谈概率。最近几年人工智能、数据科学已成为一项推动业务发展的重要技术。而进入这个领域的人,基本上一定会翻阅领域内的文章,以及参与相关任务。那么你会发现与概率有关的问题基本上绕不开:要过滤垃圾邮件,这需要贝叶斯思维;要从文本中提取出名称实体,有赖于概率图模型研究;要做语音识别,离不开随机过程中的隐马尔可夫模型;要通过样本推断某类对象的总体特征,则需要建立估计理论和大数定理;要进行统计推断,必不可少的一类应用广泛的采样方法则是蒙特卡罗方法以及马尔可夫过程的稳定性。可以看到,概率理论既是程序员面试的高频考点,又是解决实际问题的现实工具。除了理论以外,运用好 Python 工具,例如 Numpy、Scipy、Matplotlib、Pandas 等,也有助于强化对知识的理解,后面的基础知识介绍中会穿插有这方面的内容。本专栏是一个连载专栏,主要讨论程序员面试中的高频概率问题,包括知识点和题目,但不涉及机器学习中的概率进阶理论知识。其目录规划如下:第一章为序言,也就是本小节的内容。第二章简述随机模拟,并以一实例加深大家对随机模拟的思考过程,附 Python 代码。第三章简要梳理面试中概率核心考点。第四章为连载部分,将持续更新概率计算问题及其解答。第五章为附录,主讲概率在计算机中的现实应用。

3.面试题
1. 什么是 Spring Cloud Alibaba ?答案:Spring Cloud Alibaba 是 Spring 官方认证的 Spring Cloud 实现,致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里分布式应用解决方案,通过阿里中间件来迅速搭建分布式应用系统。2. Spring Cloud Alibaba 实现了哪些 Spring Cloud 特性,对应的组件分别是什么?答案:3. Spring Cloud Alibaba 提供哪些阿里云商业化组件的支持?答案:Spring Cloud Alibaba 除提供开源组件之外,还支持阿里云商业化组件,以丰富微服务和云计算的 Spring 编程模型:具体请参考 Spring Cloud Alibaba 子工程:https://github.com/alibaba/aliyun-spring-boot/4. 请说明 Spring Cloud Alibaba Nacos Discovery 的核心特性有哪些?答案:Nacos Discovery 核心特性包括:Spring Cloud Commons 服务发现和发现Reactive 服务发现Spring Boot Actuator Endpoint 和 HealthIndicatorNetflix Ribbon(复杂均衡组件)Watch 特性(Spring Cloud 心跳事件)5. 请说明 Spring Cloud Alibaba Nacos Config 的核心特性有哪些?答案:Nacos Config 核心特性包括:Spring Cloud 动态配置源(PropertySourceLocator)Spring Cloud 配置动态更新(@RefreshScope 以及历史追踪)多种配置格式解析(Properties、YAML、XML 和 JSON 等)Nacos 连接诊断分析Spring Boot Actuator Endpoint 和 HealthIndicator6. 同为服务调用组件,请说明 Spring Cloud OpenFeign 与 Dubbo Spring Cloud 的相同点和区别?答案:相同点:编程模型面向 Java 接口编程(动态生成调用代理,屏蔽底层细节)注解驱动外部化配置异步调用REST 支持服务治理:负载均衡服务熔断失败重试服务链路追踪差异点:元信息:Spring Cloud OpenFeign 需要在 Java 接口上预置 Spring MVC 注解以实现 REST 服务路由,而 Dubbo Spring Cloud 仅需要 Java 方法签名即可实现服务路由。同时,后者还能在运行时获取 Dubbo 元信息能力版本支持:Spring Cloud OpenFeign 无法实现单个 Java 接口多版本支持,Dubbo Spring Cloud 可以同时支持多版本泛化调用:Spring Cloud OpenFeign 无法支持服务的泛化调用,而 Dubbo Spring Cloud 内建 GenericService 接口予以支持,类似于 Spring Cloud @LoadBalanced RestTemplate 特性通讯协议:Dubbo Spring Cloud 支持多种通讯协议,比如 dubbo、grpc、thrift 以及 REST, Spring Cloud OpenFeign 仅支持 REST扩展特性:Dubbo Spring Cloud 承继 Apache Dubbo 高扩展特性,包括通讯协议、序列化协议、负载均衡等7. 什么是 Dubbo 服务自省?答案:服务自省是 Dubbo 应用在运行时处理和分析 Dubbo 服务元信息(Metadata)的过程,如当前应用暴露 的Dubbo 服务以及各自的通讯协议等。期间会伴随着事件的广播和处理,如服务暴露事件。Dubbo 服务自省架构是其传统架的一种补充,更是未来 Dubbo 架构,它更适合以下使用场景:超大规模 Dubbo 服务治理场景微服务架构和元原生应用Dubbo 元数据架构的基石

第三章:算法面试详解
1. Stage 1:介绍在面试开始时,大多数情况下面试官会简单介绍自己和他们在公司的角色,然后让你做自我介绍。准备并排练一段自我介绍。自我介绍应该在 30 秒内总结你的教育背景、工作经历和兴趣爱好。保持微笑,且让说话的声音听起来自信。当面试官谈论他们在公司的工作时,请注意听 - 这有助于稍后提出相关问题。如果面试官提到任何你也感兴趣的事情,无论是他们的工作还是爱好,提出来。2. Stage 2:问题陈述在自我介绍之后,面试官会给你一个问题陈述。如果您在共享文本编辑器中答题,他们很可能将问题描述和测试用例一起粘贴到编辑器中,然后将问题读给您听。确保你完全理解了这个问题。在面试官把问题读完之后,通过将其解释回给他们来确认问题在问什么。询问有关输入的问题阐述,例如:输入是只有整数,还是可以有其他类型?我能假设输入是有序的吗?输入是保证有元素还是可以为空?如果给出了无效输入,我该如何处理?询问预期的输入大小。有时候,面试官会含糊其辞,但如果他们确实给了你一个范围,这可能是一个线索。例如,如果 n 非常小,则可能是回溯。如果 n 在 100 - 1000 左右,O(n 2 ) 的解决方案可能是最优的。 如果 n 非常大, 那么你可以需要比 O(n) 更好的解决方案。提出明确的问题不仅能帮助你更好地理解问题,还能表现出对细节的关注,以及对边缘情况的考虑。3. Stage 3:头脑风暴 DS&A尝试找出适用的数据结构或算法。分解问题并尝试找到你会的常用解法。弄清楚问题需要你做什么,并考虑什么样的数据结构或算法可以以较好的时间复杂度来完成。把你的想法都说出来。这会让面试官知道你善于权衡利弊。如果问题涉及到查看子数组,那么应该考虑滑动窗口,因为每个窗口都代表一个子数组。即使你错了,面试官仍然会欣赏你的思考过程。通过把想法都说出来,面试官也可以借此给你提示,并为你指出正确的方向。一旦决定了要使用的数据结构/算法,现在就需要构造实际的算法。在编码之前,你应该考虑算法的大致步骤,向面试官解释这些步骤,并确保他们理解并同意这是一个合理的方法。通常,如果你走错了路,他们会巧妙地暗示你。在这个阶段你能接受面试官所说的话是 非常 重要的。请记住:他们知道最佳解决方案。如果他们给你提示,那是因为他们希望你成功。不要固执,准备好探索他们给你的想法。4. Stage 4:实操一旦你想出了一个算法,并让面试官同意了,就该开始写代码了。如果你打算使用一个库或模块,例如 Python 的集合,在开始之前确保面试官可以接受。当你写代码时,解释你的决策。例如,如果你正在解决一个图形问题,当你声明一个集合 seen,解释它是为了防止访问同一个节点超过一次,从而也防止了循环。编写干净的代码。每一种主流的编程语言都有一个关于代码应该如何编写的约定。确保你知道你打算使用的语言的基础知识。Google 提供了适用于所有主流语言的 总结。最重要的部分是大小写约定、缩进、空格和全局变量。避免重复代码。例如,如果您在网格上进行 DFS 操作,则应该反复使用方向数组 [(0, 1), (1, 0), (0, -1), (-1, 0)] ,而不是为每个方向编写相同的逻辑 4 次。如果你发现自己在多个地方编写类似的代码,可以考虑创建一个函数或使用循环来简化它。不要害怕使用辅助函数。它们使你的代码更加模块化,这在实际软件工程中非常重要。之后的代码说不定还会用上辅助函数。如果你遇到困难或意识到你最初的计划可能行不通,不要慌。与面试官交流你的疑虑。如果你默默地挣扎,很可能又会钻牛角尖。一种策略是首先实现一个暴力解决方案,同时承认 这是一个次优解决方案。完成后,分析算法的每个部分,找出哪些步骤 “慢”,并尝试思考如何加快速度。让面试官参与进来,让他们参与讨论 —— 他们通常愿意提供帮助。5. Stage 5:测试 & debug一旦你写完代码,你的面试官可能会想要测试你的代码。根据公司的不同,会有一些不同的环境:内置测试用例,代码需要运行这些平台类似于 LeetCode。将会有各种各样的测试用例 —— 小输入,大输入,测试边缘用例的输入。这种环境给您的代码带来了最大的压力,因为会暴露出不完美的解决方案。但是,它也为创建您自己的测试带来了最小的压力,因为测试用例已经内置在了内部。自己写测试用例,代码需要运行这些平台通常是支持运行代码的共享文本编辑器。面试官会希望你编写自己的测试用例。要真正测试代码,你应该在代码的最外层范围编写,即代码将首先运行的地方。假设你在函数中解决了问题 (就像在 LeetCode 上),你可以用你编写的测试用例调用你的函数,并将结果打印到控制台。在编写自己的测试时,请确保尝试各种测试。包括边缘情况、直觉输入和可能无效的输入 (如果面试官想让你处理这种情况)。自己写测试用例,代码不需要运行这些平台只是共享文本编辑器,不支持运行代码。面试官会希望你编写自己的测试用例,并且手动模拟运行。为了 “测试” 代码,你必须在每个测试用例中手动检查算法。试着压缩一些琐碎的部分 —— 例如,你正在创建一个前缀和,不要 字面上 遍历每个元素的 for 循环。可以这样说:“在这个 for 循环之后,我们将有一个前缀和,他是这样的……”。在遍历代码时,将函数中使用的变量写入 (在编辑器中,函数外部的某处),并不断更新它们。不管在什么情况下,如果您的代码出现了错误,不要慌!如果环境支持运行代码,请在相关位置放置打印语句以尝试识别问题。用一个小的测试用例手动遍历(就像你没有运行环境时所做的那样)。当你这样做的时候,讨论变量的期望值应该是什么,并将它们与实际值进行比较。再说一遍,你说话越多,面试官就越容易帮助你。6. Stage 6:解释与跟进在编写算法并运行测试用例之后,准备回答关于算法的问题。你应该准备好回答的问题包括:算法的时间和空间复杂度是多少?你应该从最坏的情况来考虑。但是,如果最坏的情况很少,并且平均情况的运行时明显更快,那么你还应该提到这一点。你为什么选择……?这可以是你对数据结构的选择,算法的选择,循环配置的选择。准备好解释你的思考过程。你认为算法在时间和空间复杂度上是否可以改进?如果问题需要遍历输入中的每个元素 (假设输入没有排序,需要找到最大的元素),那么你很可能无法比 O(n) 更快。否则你很可能无法比 O(logn) 更快。如果面试官问这个问题,答案 通常 是肯定的。在断言你的算法是最优的时候要小心 —— 不要轻易使用绝对的形容。如果面试还有剩余时间,你可能会被问到一个全新的问题。在这种情况下,从步骤 2(问题陈述)重新开始。但是,你也可能会被要求对你已经解决的问题进行跟进。面试官可能会引入新的约束,要求改进空间复杂度,或任何其他数量的东西。这部分是为什么真正理解解决方案而不是仅仅记住它们很重要的原因。7. Stage 7:结尾面试官通常会在面试结束时留出几分钟的时间让你问一些关于他们或公司的问题。在这一点上,很少能改善面试的结果,但你肯定能让它变得更糟。面试是双向的。你应该利用这段时间来了解这家公司,看看你是否愿意在那里工作。你应该在面试前准备一些问题,比如:在公司的一天中会做些什么?你为什么决定加入这家公司而不是另一家公司?关于这份工作,你最喜欢和最不喜欢的是什么?我可以从事什么样的工作?所有的大公司都会有自己的科技博客。展示你对这家公司感兴趣的一个好方法是阅读一些博客文章,并编制一个关于公司为什么做出这些决定的问题清单。保持兴趣,保持微笑,倾听面试官的回答,并提出后续问题,以表明你理解他们的答案。如果你没有高质量的问题,或者表现得无聊或不感兴趣,这可能会给面试官一个不好的信号。如果面试官最后不喜欢你,你在技术方面做得再好也没用。8. Stage 8:面试备考总览以下是「面试的阶段」一文的摘要。如果您进行远程面试,您可以打印此浓缩版并在面试期间将其放在您面前。第一阶段:介绍30-60 秒介绍您的教育、工作经验和兴趣。自信,保持微笑。当面试官谈论他们自己时要注意,稍后将他们的工作纳入您的问题。第二阶段:问题陈述在面试官将问题读给你听后,将问题复述给他们。询问有关输入的问题描述,例如预期的输入大小、边缘情况和无效输入。第三阶段:头脑风暴 DS&A把你所有的想法都说出来。分解问题:弄清楚你需要做什么,并思考什么数据结构或算法可以以良好的时间复杂度完成它。接受面试官的任何评论或反馈,他们可能试图暗示您找到正确的解决方案。一旦你有了想法,在编码之前,向面试官解释你的想法,并确保他们理解并同意这是一种合理的方法。第四阶段:实操在你实际编码时解释你的决策。当你声明集合之类的东西时,解释一下目的是什么。编写符合规范编程语言约定的代码。避免编写重复代码 - 如果你多次编写类似代码,请使用辅助函数或 for 循环。如果你被卡住了,不要惊慌 - 与你的面试官交流你的疑虑。不要害怕暴力解决方案(同时承认它是暴力解法),然后通过优化 “慢” 的部分来改进它。继续把你的想法说出来并与面试官交谈。这让他们更容易给你提示。第五阶段:测试 & debug遍历测试用例时,通过在文件底部写入来跟踪变量,并不断更新它们。压缩琐碎的部分,例如创建前缀和以节省时间。如果有错误并且环境支持运行代码,将打印语句放入你的算法并遍历一个小测试用例,比较变量的预期值和实际值。如果遇到任何问题,请直接说出问题并继续与面试官交谈。第六阶段:解释和跟进您应该准备回答的问题:时间和空间复杂度,平均和最坏情况。你为什么选择这个数据结构、算法或逻辑?您认为该算法可以在复杂性方面进行改进吗?如果他们问你这个问题,那么答案通常是,特别是如果你的算法比 O(n) 慢。第七阶段:结尾准备好有关公司的问题。对面试官的回答表现出感兴趣、微笑并提出后续问题。

【面试】Mysql主键索引普通索引索引和唯一索引的区别是什么?
前言在 MySQL 中, 索引是在存储引擎层实现的, 所以并没有统⼀的索引标准, 由于 InnoDB 存储引擎在 MySQL数据库中使⽤最为⼴泛, 下⾯以 InnoDB 为例来分析⼀下其中的索引模型.在 InnoDB 中, 表都是根据主键顺序以索引的形式存放的, InnoDB 使⽤了 B+ 树索引模型,所以数据都是存储在 B+ 树中的。索引用来快速地寻找那些具有特定值的记录,所有MySQL索引都以B-树的形式保存。MySQL提供多种索引类型供选择:普通索引 、唯一性索引、主键索引 、全文索引等等。下面本篇文章就来给大家介绍一下主键索引和普通索引之间的区别 ,希望对你们有所帮助。分析从图中可以看出, 根据叶子节点内容不同,索引类型分为主键索引和非主键索引.主键索引也被称为聚簇索引,叶子节点存放的是整行数据;而非主键索引被称为二级索引,叶子节点存放的是主键的值.如果根据主键查询, 只需要搜索ID这颗B+树而如果通过非主键索引查询, 需要先搜索k索引树, 找到对应的主键, 然后再到ID索引树搜索一次, 这个过程叫做回表.主键索引主键索引不可以为空主键索引可以做外键一张表中只能有一个主键索引普通索引用来加速数据访问速度而建立的索引。多建立在经常出现在查询条件的字段和经常用于排序的字段。被索引的数据列允许包含重复的值唯一索引被索引的数据列不允许包含重复的值总结非主键索引的查询需要多扫描一颗索引树, 效率相对更低.普通索引是最基本的索引类型,没有任何限制,值可以为空,仅加速查询。普通索引是可以重复的,一个表中可以有多个普通索引。主键索引是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值;索引列的所有值都只能出现一次,即必须唯一。简单来说:主键索引是加速查询 + 列值唯一(不可以有null)+ 表中只有一个。

4.概率论面试题(连载)—中
4.8仓促的决斗在某一天的决斗场,有两个人会在 5 点到 6 点之间随机的某一分钟到来,然后 5 分钟后离开。如果某一时刻这两个人同时在场,会发生决斗。问:这两个人发生决斗的概率是多少?思路参考记两个人分别为 a 和 b,如图所示,横轴 x 是 a 到达的时间,纵轴 y 是 b 到达的时间,到达的时间范围均为 0 ~ 1。(x, y) 会随机地在图中的正方形区域中产生,如果两个人的时间间隔小于 1/12 则会发生决斗,也就是 abs(x - y) < 1/12,这对应于图中的阴影部分。(x, y) 落在阴影的概率是阴影面积与正方形面积的比值。蒙特卡洛模拟import numpy as np from multiprocessing import Pool def test(T): array_arrival_times = 60 * np.random.random(size=(T, 2)) array_diff = np.abs(np.diff(array_arrival_times)) return np.mean(array_diff <= 5) T = int(1e7) args = [T] * 8 pool = Pool(8) ts = pool.map(test, args) print("发生决斗的概率: {:.6f}".format(sum(ts) / len(ts)))模拟结果发生决斗的概率: 0.1598054.9帮助赌徒戒赌布朗沉迷于轮盘赌,并且强迫症地总是在 13 这个数字上押注 1 美元。轮盘赌有 38 个等概率的数字,如果玩家押注的数字出现,则拿到 35 倍投注金额,加上原始投注金额,共 36 倍,如果押注的数字没出现,则损失原始投注金额。好朋友为了帮助布朗戒赌,设计了一个赌注,与布朗等额投注 20 美元(好朋友和布朗均需要出 20 美元),赌以下事情:布朗在第 36 次游戏完成之后会亏钱(如果果真亏钱了,则朋友得到双方的筹码共 40 美元,否则布朗得到这 40 美元)。问:这个戒赌方案是否有效(如果布朗能挣钱,说明无效,如果布朗会亏钱则说明有效)。思路参考布朗每次游戏的获胜概率为 p = 1/38,期望从赌场拿回的钱为 36 * p。考虑 36 次游戏。布朗需要付出 36 美元与赌场的押注金额,以及 20 美元与朋友的押注金额。下面我们看布朗期望能够拿回多少钱。布朗拿回的钱中,一部分来自赌场,另一部分来自朋友。来自赌场的部分,布朗每次期望拿回 36 * p,36 次期望拿回 36 * 36 * p。下面考虑来自朋友的部分:每一次获胜,布朗会获得 36 美元。也就是说布朗只要赢一次就已经不亏了,也就意味着他只要不是 36 场全输了,就可以从朋友那里拿回 40 美元。布朗在 36 次游戏中至少赢一次的概率为:1−(1−p) 36因此布朗期望从好朋友那里拿回的金额为 40 * (1 - (1 - p) ^ 36)把两部分加在一起,布朗在 36 次游戏中拿回金额的期望(平均可拿到金额)为E=40(1−(1−p) 36 )+36×36×p这个数只要大于 36 + 20 = 56 美元,那么朋友设计的这个戒赌方案反而会助长布朗去赌博,戒赌方案就无效。下面我们编程计算这个数def E(p): q = (1 - p) ** 36 return 40 * (1 - q) + 36 * 36 * p income = E(1 / 38) print("Except Income: {:.6f}".format(income))计算结果Except Income: 58.790419由于期望能拿回 58.790419 美元,比 56 美元多,布朗能够挣钱,朋友设计的戒赌方案无效。蒙特卡洛模拟from multiprocessing import Pool import numpy as np p = 1/38 def game1(): # 1 次游戏,返回游戏结束拿回的钱 if np.random.rand() < p: return 36 return 0 def game36(): # 36 次游戏,返回36次游戏结束拿回的钱 w = 0 for _ in range(36): w += game1() if w >= 36: return w + 40 return w def test(T): np.random.seed() total = 0 for _ in range(T): total += game36() return total / T T = int(1e6) args = [T] * 8 pool = Pool(8) incomes = pool.map(test, args) for x in incomes: print("Average Income: {:.6f}".format(x))模拟结果Average Income: 58.894648 Average Income: 58.697580 Average Income: 58.741948 Average Income: 58.745660 Average Income: 58.757380 Average Income: 58.819856 Average Income: 58.800196 Average Income: 58.7672884.10 较短的一节木棍一根棍子,随机选个分割点,将棍子分为两根。a. 较小的那一根的长度平均是多少?b. 较小的那一根与较长的那一根的比例平均是多少?思路参考假设木棍长度为 1,分隔点 X 是 (0, 1) 上的均匀分布,也就是 X ~ U(0, 1),概率密度函数 fX(x)=1, 0 < x < 1。记较短的那一根的长度为 Y,y 与 x 的关系如下下面我们来看 (a) 和 (b) 两问:(a)要求 Y 的期望,我们还需要两个知识点,一个是连续型随机变量的期望,一个是连续型随机变量函数的概率密度函数。知识点复习(1) 连续型随机变量的期望(2) 随机变量函数的概率密度函数X 的概率密度函数为 f X(x),Y = g(X),Y 的概率密度函数是多少?解决这个问题可借助下面通用流程:根据 X 的范围(记作 Rx)求出 Y 的范围(记作 Ry);对 Ry 中的每一个 y,求出分布函数 FY(y)。【一般用积分的方法】FY(y)=P(Y≤y)=P(g(X)≤y)=P(X∈Ry)=∫ RyfX(x)dx其中 x∈Ry 与 g(X)≤y 是相同的随机事件。Ry={x:g(x)≤y}对分布函数 FY(y) 求导得到密度函数 f Y(y)。而如果 g(X) 是严格单调函数,x = h(y) 为相应的反函数,且可导,则 Y = g(X) 的密度函数可以直接给出计算有了以上两个知识点,我们就可以开始计算了,下面我们推导 Y 的概率密度函数。g 是一个分段函数,在 (0, 1/2) 上,Y = X,单调递增,在 (1/2, 1) 上,Y = 1 - X,单调递减。因此我们可以对这两个范围分别来看。(1) 对于 0 < x < 1/2在 0 < x < 1/2 范围内,y = g(x) = x,因此 0 < y < 1/2,其反函数 x = h(y) = y,导数 h'(y) = 1。y 的密度函数如下fY(y)=fX(h(y))∣h′(y)∣=1y 的期望如下(2) 对于 1/2 < x < 1在 1/2 < x < 1 范围内,y = g(x) = 1 - x,因此 0 < y < 1/2,其反函数 x = h(y) = 1 - y,导数 h'(y) = -1。y 的密度函数如下fY(y)=fX(h(y))∣h′(y)∣=1y 的期望如下将两部分加到一起即可得到所求的期望值,答案为 1/4。(b)要求较小的那一根与较长的那一根的比例的期望,也就是求 y / (1 - y) 的期望。由 (a),我们已经知道 Y 的范围是 (0, 1/2),并且 f Y(y) 根据 X 的取值来源于两部分,第一部分是 1 < X < 1/2,第二部分是 1/2 < X < 1。将 X 取值在这两个范围时的 f Y(y) 相加,可以得到:f Y (y)=2y∈(0,1/2)这里 f Y(y)=2,但是 y 的取值是 (0, 1/2),这是一个均匀分布。基于 Y,我们定义随机变量 Z = Y / (1 - Y)g(y) 是严格单调的,其反函数为 h(z) = z / (z + 1),其导数为 ℎ′(z)=(z+1) −2 , z 的范围为 (0, 1),因此 z 的密度函数如下fZ(z)=fY(h(z))∣h′(z)∣=2×(z+1)−2Z 的期望如下蒙特卡洛模拟import numpy as np def pyfunc(x): """ 0 < x < 1 """ if x > 0.5: x = 1 - x return x def pyfunc2(x): """ 0 < x < 0.5 """ return x / (1 - x) func = np.frompyfunc(pyfunc, 1, 1) func2 = np.frompyfunc(pyfunc2, 1, 1) def test(T): X = np.random.uniform(0, 1, T) X = func(X) Y = func2(X) return (np.mean(X), np.mean(Y)) T = int(1e7) m1, m2 = test(T) print("较短的木棍的期望长度: {:.6f}".format(m1)) print("较短的木棍与较长的木根长度比值的期望: {:.6f}".format(m2))模拟结果较短的木棍的期望长度: 0.249920 较短的木棍与较长的木根长度比值的期望: 0.3861874.11完美手牌从一副洗好的牌(52张)中抽 13 张,拿到完美手牌(拿到的 13 张是同花色)的概率是多少。思路参考52 张牌一共有 52! 中排列顺序。完美手牌需要抽取的 13 张是同一花色,这 13 张同一花色的牌有 13! 种排列顺序,未被抽到的 39 张牌有 39! 中排列顺序。因此对于指定花色,所有抽到的牌都是这一指定花色的概率为一共有四种花色,因此拿到完美手牌的概率为编程计算from math import factorial as fac p = 4.0 * fac(13) * fac(39) / fac(52) print("{:.6e}".format(p))计算结果6.299078e-124.12双骰子赌博双骰子赌博是美国的流行游戏,规则如下:每次同时投掷两个骰子并且只看点数和。第一次投掷的点数和记为 x。若 x 是 7 或 11,玩家获胜,游戏结束;若 x 是 2、3 或 12,则输掉出局,其余情况则需要继续投掷。从第二次投掷开始,如果投掷出第一次的点数和 x,则获胜;如果投掷出 7,则输掉出局。重复投掷直至分出胜负。问:获胜的概率是多少?思路参考首先考虑第一次投掷第一次投掷一共有直接输,直接赢,需要继续投掷三种可能性。(1) 直接输的情况是两枚骰子的和为 2、3、12,两个骰子各自的点数情况有 (1, 1), (2, 1), (1, 2), (6, 6) 这 4 中可能性。而两个骰子各自点数共有 6 * 6 = 36 种可能性,因此第一次投掷直接输的概率为 1/9。(2) 直接赢的情况是两枚骰子的和为 7, 11,两个骰子各自的点数情况有 (1, 6), (2, 5), (3, 4), (4, 3), (5, 2), (6, 1), (5, 6), (6, 5) 共 8 种可能性,概率 2/9。(3) 剩下 2/3 概率的情况,需要继续投掷,我们记此前的第 1 次投掷的点数和为 x。考虑第二次以及后续可能的投掷从第 2 次开始,以后每次投掷的输、赢、继续投掷的条件都一样。因此我们可以只考虑第 2 次投掷,如果投掷结果是需要继续投掷,递归处理即可。记某次投掷直接赢的概率为 p(x),与第一次的投掷结果 x 有关,直接输的概率为 q,与第一次的投掷结果 x 无关,则需要继续投掷的概率为 (1 - p(x) - q),下面我们看输和赢的具体条件:(1) 输的情况是投掷出 7,两个骰子各自的点数情况共有 (1, 6), (2, 5), (3, 4), (4, 3), (5, 2), (6, 1) 共 6 种情况,概率 q = 1 / 6。(2) 赢的情况是投掷出 x。两个骰子各自的点数情况需要看 x。下面我们枚举 x 可能取值(4、5、6、8、9、10):x = 4: (1, 3), (2, 2), (3, 1),概率为 1/12;x = 5: (1, 4), (2, 3), (3, 2), (4, 1),概率为 1/9;x = 6: (1, 5), (2, 4), (3, 3), (2, 4), (5, 1),概率为 5/36;x = 8: (2, 6), (3, 5), (4, 4), (5, 3), (6, 2),概率为 5/36;x = 9: (3, 6), (4, 5), (5, 4), (6, 3),概率为 1/9;x = 10: (4, 6), (5, 5), (6, 4),概率为 1/12。总结一下,直接赢的概率 p(x) 与 x 的所有可能取值的对应关系如下p(4) = 1/12 p(5) = 1/9 p(6) = 1/36 p(8) = 1/36 p(9) = 1/9 p(10) = 1/12(3) 剩下的情况是需要继续投掷的,概率为 (1 - p(x) - q)综合以上 3 条,从第二次投掷开始,最终能赢有两种情况,一种是第二次投掷直接赢,另一种是需要继续投掷,并在后续投掷中赢了。记 P(x) 为从第二次投掷开始,最终会赢的概率。将第一次投掷直接赢和进入第二次投掷并最终赢这两种情况合起来,就是最终能赢的两种情况,概率记为 prob下面编程计算 prob 这个数mapping = { 4: 1/12, 5: 1/9, 6: 5/36, 8: 5/36, 9: 1/9, 10: 1/12, } q = 1/6 prob = 2/9 for x, p in mapping.items(): P = p / (p + q) prob += p * P print("Probability of win: {:.6f}".format(prob))计算结果Probability of win: 0.492929蒙特卡洛模拟from multiprocessing import Pool import numpy as np def throw2(x0): # 第二次以后的投掷 x1 = np.random.randint(1, 7) x2 = np.random.randint(1, 7) x = x1 + x2 if x == 7: return 0 elif x == x0: return 1 else: return throw2(x0) def throw1(): # 第一次投掷 x1 = np.random.randint(1, 7) x2 = np.random.randint(1, 7) x = x1 + x2 if x == 7 or x == 11: return 1 elif x == 2 or x == 3 or x == 12: return 0 else: return throw2(x) def test(T): np.random.seed() n = 0 for _ in range(T): n += throw1() return n / T T = int(1e7) args = [T] * 8 pool = Pool(8) probs = pool.map(test, args) for prob in probs: print("Probability of win: {:.6f}".format(prob))模拟结果Probability of win: 0.492935 Probability of win: 0.492876 Probability of win: 0.492793 Probability of win: 0.492726 Probability of win: 0.493062 Probability of win: 0.493019 Probability of win: 0.493142 Probability of win: 0.4929614.13 收集优惠券每个盒子中有一张优惠券,优惠券共有 5 种,编号为 1 到 5。必须收集所有5张优惠券才能获得奖品。问:收集到一套完整的优惠券平均需要打开多少盒子?思路参考单独考虑每个优惠券。记 T1 为一个随机变量,表示拿到第一个编号所需盒子数。P(T1 = 1) = 1,E(T1) = 1记 T2 为一个随机变量,表示拿到第二个编号所需要的额外盒子数。根据全期望公式这里 X 表示新开盒子编号是否见过,共有两种情况:前面开盒子时见过,概率为 1/5,此时平均需要的额外盒子数(即期望)为 1 + E(T2);【可参考$4-4 方法二、有向图总结】前面开盒子时没见过,概率为 4/5,此时平均需要的额外盒子数为 1。记 T3 为一个随机变量,表示从第二个编号开始到拿到第三个新编号所需的额外盒子数。根据全期望公式这里的 X 是新开的盒子的数字是否是见过的那 2 个。共有两种情况是前面见过的那2个,概率 2/5,此时额外的盒子数的期望是 1 + E(T3)不是前面见过的那2个,概率 3/5,此时额外的盒子数就是 1记 T4 为一个随机变量,表示从第三个编号开始到拿到第四个新编号所需的额外盒子数。根据全期望公式这里 X 表示新开盒子编号是否已出现过,分为两种情况:前面开盒子时见过,概率为 3/5,此时平均需要额外盒子数(期望)为 1 + E(T4);前面开盒子时没见过,概率为 2/5,此时平均需要额外盒子数(期望)为 1。记 T5 为一个随机变量,表示从第四个编号开始到拿到第五个新编号所需的额外盒子数。根据全期望公式这里 X 表示新开盒子编号是否已出现过,分为两种情况:前面开盒子时见过,概率为 4/5,此时平均需要额外盒子数(期望)为 1 + E(T5);前面开盒子时没见过,概率为 1/5,此时平均需要额外盒子数(期望)为 1。收集到一套完整的优惠券需要打开的盒子个数的期望为 E1 + E2 + E3 + E4 + E5 = 137/12 = 11.416667蒙特卡洛模拟import numpy as np import operator from multiprocessing import Pool from functools import reduce def run(): setting = set() n = 0 while True: n += 1 x = np.random.randint(1, 6) if x in setting: continue setting.add(x) if len(setting) == 5: break return n def test(T): np.random.seed() y = (run() for _ in range(T)) n = reduce(operator.add, y) return n / T T = int(1e7) args = [T] * 8 pool = Pool(8) ts = pool.map(test, args) for t in ts: print("{:.6f} coupons on average are required".format(t))11.415423 coupons on average are required 11.417857 coupons on average are required 11.415529 coupons on average are required 11.417148 coupons on average are required 11.416793 coupons on average are required 11.413937 coupons on average are required 11.416149 coupons on average are required 11.416380 coupons on average are required4.14 第 2 强的选手拿亚军一场由 8 个人组成的淘汰赛,对阵图如上图。8 个人随机被分到图中的 1~8 号位置。第二强的人总是会输给最强的人,同时也总是会赢剩下的人。决赛输的人拿到比赛的亚军。问: 第二强的人拿到亚军的概率是多少?思路参考第二强的人拿到亚军,等价于最强的人和第二强的人在决赛遇到,等价于这两个人一个被分到 1~4,另一个被分到 5~8。x1: 最强的人被分到 1~4x2: 最强的人被分到 5~8y1: 第二强的人被分到 1~4y2: 第二强的人被分到 5~8第二强的人获得亚军的概率为 2/7+2/7=0.571428蒙特卡洛模拟import numpy as np import operator from multiprocessing import Pool from functools import reduce ladder = range(8) def run(): l = np.random.permutation(ladder) x = int(np.where(l == 0)[0][0]) y = int(np.where(l == 1)[0][0]) return (x <= 3 and y >= 4) or (x >= 4 and y <= 3) def test(T): np.random.seed() y = (run() for _ in range(T)) n = reduce(operator.add, y) return n / T T = int(1e7) args = [T] * 8 pool = Pool(8) ts = pool.map(test, args) for t in ts: print("P(the second-best player wins the runner-up cup): {:.6f}".format(t))模拟结果P(the second-best player wins the runner-up cup): 0.571250 P(the second-best player wins the runner-up cup): 0.571030 P(the second-best player wins the runner-up cup): 0.571432 P(the second-best player wins the runner-up cup): 0.571021 P(the second-best player wins the runner-up cup): 0.571379 P(the second-best player wins the runner-up cup): 0.571495 P(the second-best player wins the runner-up cup): 0.571507 P(the second-best player wins the runner-up cup): 0.5712264.15 孪生骑士(a)8 个骑士进行淘汰赛,对阵图与上图这样的网球对阵图类似。8 个骑士的水平一样,任意一组比赛双方的获胜概率均为 0.5。8 个骑士中有两个人是孪生兄弟。问:这两个孪生兄弟在比赛过程中相遇的概率?(b)将 8 个骑士的淘汰赛改为 2^n 个人的淘汰赛,问这两个孪生兄弟相遇的概率?思路参考(a)8 个骑士的比赛共有 3 轮,我们一轮一轮地考虑。记事件 X1 为孪生骑士在第一轮相遇,若想在第一轮相遇,则它们必须同时被分到 (1, 2), (2, 1), (3, 4), (4, 3), (6, 5), (5, 6), (8, 7), (7, 8) 这八种情况之一。概率为记事件 X2 为孪生骑士在第二轮相遇,若想在第二轮相遇,则需要它们两个被分到以下十六种情况的一种,同时它们两个还要都赢了(1, 3), (1, 4), (2, 3), (2, 4), (5, 7), (5, 8), (6, 7), (6, 8) (3, 1), (4, 1), (3, 2), (4, 2), (7, 5), (8, 5), (7, 6), (8, 6) 概率为记事件 X3 为孪生骑士在第三轮相遇,若想在第三轮相遇,需要它们两个其中一个被分到 [1..4] 中的一个,另一个被分到 [5..8] 中的一个。且他们在前两轮的共四场比赛都要赢,概率为把 X1, X2, X3 这三种情况的概率加起来,得到孪生骑士相遇的概率(b)记事件 Xi 为孪生骑士在第 i 轮相遇,其中 i 取值为 1, 2, ..., n,共 T = 2^n 人。对第 i 轮,将 1 ~ 2^n 连续地分为 B = (2^n)/(2^(i-1)) 个桶,每个桶 C = 2^(i-1) 个元素。孪生骑士需要在编号为 (2k+1, 2k+2) 的相邻桶中(k=0,1,...,(2^n)/(2^i) - 1),相邻桶的组数为 N = (2^n)/(2^i),孪生骑士在满足要求的桶中的概率为孪生骑士在第 1,2,...,i-1 轮中,共有 (i-1) * 2 场比赛。这些比赛还要赢,概率为将两个概率相乘就是孪生骑士在第 i 轮相遇的概率孪生骑士相遇的概率为蒙特卡洛模拟import numpy as np import operator from multiprocessing import Pool from functools import reduce def P(n): return 1 / (2 ** (n - 1)) def run(n): l = np.random.permutation(ladder) for _ in range(1, n + 1): for i in range(0, len(l), 2): if l[i] + l[i + 1] == 1: return 1 if np.random.rand() < 0.5: l[int(i / 2)] = l[i] else: l[int(i / 2)] = l[i + 1] l = l[:int(len(l) / 2)] return 0 def test(n): T = int(1e5) np.random.seed() y = (run(n) for _ in range(T)) return reduce(operator.add, y) / T def main(n): print("n = {}: P(n) = {}".format(n, P(n))) args = [n] * 8 pool = Pool(8) ts = pool.map(test, args) for t in ts: print("P(twin knight meet): {:.6f}".format(t)) print() for n in range(3, 7): global ladder ladder = range(2 ** n) main(n)模拟结果n = 3: P(n) = 0.25 P(twin knight meet): 0.249970 P(twin knight meet): 0.249815 P(twin knight meet): 0.250011 P(twin knight meet): 0.249984 P(twin knight meet): 0.249990 P(twin knight meet): 0.250079 P(twin knight meet): 0.249889 P(twin knight meet): 0.250108 n = 4: P(n) = 0.125 P(twin knight meet): 0.124822 P(twin knight meet): 0.124963 P(twin knight meet): 0.124807 P(twin knight meet): 0.125172 P(twin knight meet): 0.124867 P(twin knight meet): 0.124937 P(twin knight meet): 0.124887 P(twin knight meet): 0.124992 n = 5: P(n) = 0.0625 P(twin knight meet): 0.062401 P(twin knight meet): 0.062294 P(twin knight meet): 0.062492 P(twin knight meet): 0.062508 P(twin knight meet): 0.062588 P(twin knight meet): 0.062417 P(twin knight meet): 0.062393 P(twin knight meet): 0.062459 n = 6: P(n) = 0.03125 P(twin knight meet): 0.031231 P(twin knight meet): 0.031373 P(twin knight meet): 0.031206 P(twin knight meet): 0.031259 P(twin knight meet): 0.031189 P(twin knight meet): 0.031246 P(twin knight meet): 0.031311 P(twin knight meet): 0.0312564.16有放回抽样还是无放回抽样两个桶 A, B,桶内有红色和黑色的球,其中:A 桶有 2 个红球和 1 个黑球。B 桶有 101 个红球和 100 个黑球。随机选出一个桶放到你面前,然后从中抽样两个球,基于这两个球的颜色猜该桶是 A 还是 B。你可以选择有放回抽样或者无放回抽样,也就是你可以决定在抽取第二个球之前,是否将抽出的第一个球放回去再抽第二次。问:为了使得猜中桶名的概率最大,应该有放回抽样还是无放回抽样,猜中的概率是多少?思路参考本题的目标是根据抽样两个球的结果,来猜当前是哪个桶。有两种获取证据的方式,一种是有放回抽样抽两个球,另一种是无放回抽样抽两个球。我们要选择使得猜中概率更大的那个抽样方式。根据第 3 章总结,我们应先确定“证据是什么”、“假设有哪些”,然后计算似然值,再推公式计算。(1) 假设和证据首先我们看都有哪些可能的证据。由于抽样两个球,且球的种类有红和黑,因此无论有放回抽样还是无放回抽样,观察到的证据都可能有 ”两红”、”一红一黑”、”两黑” 这三种,分别记为 e1, e2, e3。然后我们看都有哪些可能的假设。由于有 A 和 B 这两种桶,因此针对随机取出的桶有两种假设:桶名为 A 和 桶名为 B,分别记为 H1, H2。不论抽样是否有放回,都有上述的三种证据和两种假设。(2) 先验概率由于一共两个桶,随机抽取一个,因此抽到 A 或 B 的先验概率为 1/2,与抽样是否有放回无关。(3) 似然值有放回抽样对于 A 桶,有放回抽样两个球,得到 e1, e2, e3 三种证据的概率分别为对于 B 桶,有放回抽样两个球,得到 e1, e2, e3 三种证据的概率分别为无放回抽样对于 A 桶,无放回抽样两个球,得到 e1, e2, e3 三种证据的概率分别为对于 B 桶,无放回抽样两个球,得到 e1, e2, e3 三种证据的概率分别为(4) 推导 P(T)记 P(T) 为猜测正确的概率。P(T|e) 表示获取到的证据为 e 时,猜测正确的概率。其中 P(ei|Hj) 是我们之前算好的似然值,P(Hj) 是之前算好的先验概率。(5) 计算我们分别计算在有放回抽样和无放回抽样 2 次时,预测正确的概率 P(T)有放回抽样无放回抽样蒙特卡洛模拟import numpy as np from multiprocessing import Pool class Box: def __init__(self, nR, nB, t): # 当前子在盒子中的 self.nR = nR self.nB = nB # 抽出未放回的 self.mR = 0 self.mB = 0 self.t = t def sample_with_replacement(self): if np.random.randint(0, self.nR + self.nB) < self.nR: return "R" else: return "B" def sample_without_replacement(self): if np.random.randint(0, self.nR + self.nB) < self.nR: self.nR -= 1 self.mR += 1 return "R" else: self.nB -= 1 self.mB += 1 return "B" def reset(self): self.nB += self.mB self.mB = 0 self.nR += self.mR self.mR = 0 def sample(self, method): if method == "with": return self.sample_with_replacement() else: return self.sample_without_replacement() class Model: def __init__(self, boxs, method): self.boxs = boxs self.method = method # mapping 记录每种证据下两种盒子种类的次数 # 共有 6 个参数 self.mapping = {"RR": np.array([0, 0]) ,"RB": np.array([0, 0]) ,"BB": np.array([0, 0]) } def update(self, e, idx): """ 根据数据 (e, idx) 更新 mapping 的参数 数据的含义是证据 e 对应硬币种类 idx """ self.mapping[e][idx] += 1 def train(self): """ 随机生成数据训练 """ n_train = int(1e5) for i in range(n_train): idx = np.random.randint(0, len(self.boxs)) box = self.boxs[idx] # 抽取两次,获取证据 e = [box.sample(self.method), box.sample(self.method)] if e[0] == "B" and e[1] == "R": e[1], e[0] = e[0], e[1] e = "".join(e) self.update(e, box.t) box.reset() for k, v in self.mapping.items(): print("证据 {} 下的A桶有{}个, B桶有{}个".format(k, v[0], v[1])) print("===训练结束===") def predict(self, e): """ 根据证据 e 猜盒子种类,返回 0 或 1 """ return np.argmax(self.mapping[e]) def test(self, T): correct = 0 np.random.seed() for _ in range(T): # 随机选盒子 idx = np.random.randint(0, len(self.boxs)) box = self.boxs[idx] # 抽取 2 次,获取证据 e = [box.sample(self.method), box.sample(self.method)] if e[0] == "B" and e[1] == "R": e[1], e[0] = e[0], e[1] e = "".join(e) # 根据证据猜硬币种类 if self.predict(e) == box.t: correct += 1 box.reset() return correct / T class Solver: def __init__(self, method): self.method = method self.boxs = [Box(2, 1, 0), Box(101, 100, 1)] self.ans = -1 def __call__(self): model = Model(self.boxs, self.method) model.train() T = int(1e7) args = [T] * 8 pool = Pool(8) ts = pool.map(model.test, args) self.ans = sum(ts) / len(ts) solver1 = Solver("with") solver1() print("有放回时,P(T): {:.6f}".format(solver1.ans)) solver2 = Solver("without") solver2() print("无放回时,P(T): {:.6f}".format(solver2.ans))模拟结果证据 RR 下的A桶有22280个, B桶有12755个 证据 RB 下的A桶有21997个, B桶有24808个 证据 BB 下的A桶有5561个, B桶有12599个 ===训练结束=== 有放回时,P(T): 0.596081 证据 RR 下的A桶有16452个, B桶有12755个 证据 RB 下的A桶有33433个, B桶有25112个 证据 BB 下的A桶有0个, B桶有12248个 ===训练结束=== 无放回时,P(T): 0.623070

4.概率论面试题(连载)—上
4.1抽屉中的袜子抽屉里有红袜子和黑袜子,随机抽出两只,两只全红的概率为 1/2。问:a. 抽屉里的袜子最少有多少只?b. 如果黑袜子为偶数只,则抽屉里的袜子最少有多少只?思路参考a设抽屉里有 x 只红袜子,y 只黑袜子,总共 N = x + y 只袜子,记随机抽两只均为红色的概率为 P问题变成解以上不定方程 N > 2 的最小正整数解解析解由于对于 y > 0, x > 1,有以下不等式再结合我们可以得到不等式从左半部分不等式可以计算出从右半部分不等式可以计算出因此对于 y = 1, x 的范围在,所以 x = 3 是一个候选答案数值解暴力解法代码如下, 从 y = 1, x = 2 开始枚举,判断是否满足方程。y = 1 found = False while True: x = 2 while True: N = x + y if 2 * x * (x - 1) == N * (N - 1): print("N: {}, x: {}".format(x, N)) found = True break elif 2 * x * (x - 1) > N * (N - 1): break x += 1 if found: break y += 1得到 N = 4, x = 3b解析解利用在此前已经推出的以下公式这里规定了 y 是偶数。依次考虑 y = 2, 4, 6, ...,对每一个 y,我们可以知道 x 的范围,这个范围的长度正好为 1,因此也就是可以知道对应的 x 具体是多少。考察这对 (x, y) 是否满足 P = 1/2 即可。第一个满足的就是答案。数值解依然用暴力法解,只需要把 y 的初始值改为 2, 每轮 y 的增加量改为 2 即可y = 2 found = False while True: x = 2 while True: N = x + y if 2 * x * (x - 1) == N * (N - 1): print("N: {}, x: {}".format(x, N)) found = True break elif 2 * x * (x - 1) > N * (N - 1): break x += 1 if found: break y += 2 得到 N = 21, x = 154.2 轻率的陪审团有一个 3 人组成的陪审团,其中两个人独立做决定均有 p 概率做对,另一个人通过抛硬币做决定还有一个 1 人的陪审团,那个人做决定也有 p 概率做对。问:哪个陪审团更优可能做出正确决定?思路参考第一个陪审团做出正确决定的概率记为 P1, 第二个陪审团做出正确决定的概率记为 P2。因此 P1 = P2,两个陪审团做出正确决定的可能性相等。构造数据验证T = 10 n_trails = int(1e6) for t in range(T): p = np.random.random() # 模拟第一个陪审团 3 个人各自做出的判断,1为正确判断 # 模拟 n_trails 次 c1 = np.random.binomial(1, p, size=n_trails) c2 = np.random.binomial(1, p, size=n_trails) c3 = np.random.binomial(1, 0.5, size=n_trails) # c 中记录了 n_trails 次模拟中每次是否做出了正确判断 c = ((c1 + c2 + c3) >= 2).astype(int) print("P2 = {:.5f}, P1 = {:.5f}".format(p, np.mean(c)))实验结果P2 = 0.34158, P1 = 0.34095 P2 = 0.55780, P1 = 0.55776 P2 = 0.07460, P1 = 0.07439 P2 = 0.01918, P1 = 0.01919 P2 = 0.39175, P1 = 0.39233 P2 = 0.82699, P1 = 0.82643 P2 = 0.84726, P1 = 0.84769 P2 = 0.00396, P1 = 0.00395 P2 = 0.09100, P1 = 0.09023 P2 = 0.45738, P1 = 0.457414.3 系列赛中连续获胜Elmer 如果在三场的系列赛中赢下连续的两场,则可以得到奖励,系列赛的对手安排有两种爸爸-冠军-爸爸冠军-爸爸-冠军这里冠军的水平高于爸爸,问:Elmer 应该选择哪一组,得到奖励的概率更大?思路参考一方面,由于冠军水平比爸爸高,应该尽可能少与冠军比赛,另一方面,中间的那一场是更重要的一场,因为如果中间那场输了就不可能连赢两场。Elmer 与爸爸比赛获胜概率为 P1,与冠军比赛获胜概率为 P2,由于冠军水平高于爸爸,所以 P1 > P2。对于一组系列赛,Elmer 要得到奖励有两种可能性,一种是前两场都赢了,此时不用看第三场,另一种是第一场输了,但第二第三场都赢了。下面分别考虑两组系列赛,计算得到奖励的概率第一组系列赛,前两场都赢的概率为 P1 * P2,第一场输但是第二、三场都赢的概率为 (1 - P1) * P2 * P1,得到奖励概率记为 PaPa=P1×P2+(1−P1)×P2×P1第二组系列赛,前两场都赢的概率为 P2 * P1,第一场输但是第二、三场都赢的概率为 (1 - P2) * P1 * P2,得到奖励概率记为 PbPb=P2×P1+(1−P2)×P1×P2做差比零Pa - Pb = (P1 \times P2 + (1 - P1) \times P2 \times P1) - (P2 \times P1 + (1 - P2) \times P1 \times P2) \\ = ((1 - P1) - (1 - P2)) \times P1 \times P2 \\ = (P2 - P1) \times P1 \times P2 \\ &< 0 \\所以 Pa < Pb,Elmer 应该选择第二组系列赛。构造数据验证import numpy as np step = 0.2 for P1 in np.arange(step, 1 + step, step): for P2 in np.arange(step, P1 - step, step): print("P1: {:.2f}, P2: {:.2f}".format(P1, P2)) Pa = P1 * P2 + (1 - P1) * P2 * P1 Pb = P2 * P1 + (1 - P2) * P1 * P2 print("Pa: {:.2f}, Pb: {:.2f}".format(Pa, Pb)) print("----")结果如下:可看到 Pb 的值始终比 Pa 大P1: 0.60, P2: 0.20 Pa: 0.17, Pb: 0.22 ---- P1: 0.60, P2: 0.40 Pa: 0.34, Pb: 0.38 ---- P1: 0.80, P2: 0.20 Pa: 0.19, Pb: 0.29 ---- P1: 0.80, P2: 0.40 Pa: 0.38, Pb: 0.51 ---- P1: 0.80, P2: 0.60 Pa: 0.58, Pb: 0.67 ---- P1: 1.00, P2: 0.20 Pa: 0.20, Pb: 0.36 ---- P1: 1.00, P2: 0.40 Pa: 0.40, Pb: 0.64 ---- P1: 1.00, P2: 0.60 Pa: 0.60, Pb: 0.84 ---- P1: 1.00, P2: 0.80 Pa: 0.80, Pb: 0.96 ----4.4 试验直到第一次成功一枚骰子掷出第一个 6 平均需要掷多少次。思路参考 1记 p(k) := 第一个 6 需要掷 k 次的概率。p := 在一次投掷中得到 6 的概率;q := 在一次投掷中不是 6 的概率,由定义 q = 1 - p。k = 1: p(1) = p k = 2: p(2) = pq k = 3: p(3) = pq^2 ...总结规律后可以得到p(k)=pq k−1数学期望为处理上面的级数的技巧如下,首先两边都乘以 q。让两式相减其中几何级数那部分的公式如下将上面的公式代入到两式相减的公式中将 p = 1/6 代入,E(k) = 6思路参考 2这里我们仍沿用思路参考 1 的数学记号。记 m 为第一个 6 平均需要的次数(即期望)。由全期望公式【随机变量 k 的期望就是各种条件下 k 的期望加权求和】。这里条件期望有两种情况:1)当第一次投掷成功时,则需要的平均次数是 1(该情况下期望是 1,对应权重即发生概率为 p);2)当第一次投掷失败时,则需要的平均次数是 1 + m (该情况下期望是 1 + m,权重为 q),可列出如下等式:蒙特卡洛模拟验证结果import numpy as np p = 1/6 def test(): T = int(1e6) mapping = {} for t in range(T): i = 1 while True: if np.random.rand() < p: break i += 1 if i not in mapping: mapping[i] = 0 mapping[i] += 1 E = 0 for k in mapping: E += k * (mapping[k] / T) print("Average times is {:.4f}".format(E)) for i in range(5): test()实验结果Average times is 6.0025 Average times is 5.9918 Average times is 6.0018 Average times is 6.0038 Average times is 6.00594.5 装备升级回顾在之前 试验直到第一次成功 中解决的一个问题,问题和解法如下一枚骰子掷出第一个 6 平均需要掷多少次。当时我们用两种方法解决了这个问题:期望的定义第一种是直接用期望的定义求期望由于 i 的所有可能取值和 p(i) 我们都可以算出来,于是通过定义再加上一些公式推导技巧我们求出了 E(X) 的解析解。全期望公式第二种是基于 全期望公式 求期望当时在那篇文章中的思路是这么写的记 m 为第一个 6 需要的次数当第一次投掷成功时,则需要的平均次数是 1当第一次投掷失败时,则需要的平均次数是 1 + m于是有以下公式,进而可以求出 mm = p * 1 + q * (1 + m)从全期望公式的角度看X 是掷出第一个 6 的次数,m 就是 E(X);Y 是第一次投掷出的点数是否为 6,是则记 Y=1,否则记 Y=0;Y=1 的概率为 p,Y=0 的概率为 q;E(X|Y=1) = 1,E(X|Y=0) = 1 + E(X)代入公式即可得到解题思路中用的公式 m = p * 1 + q * (1 + m)E(X)=p(Y=1)E(X∣Y=1)+p(Y=0)E(X∣Y=0) =p×1+q×(1+E(X))有向图全期望公式的应用难点在于找出 E(X|y)。上述问题中 E[X| y=0] = (1 + E(X)), E[X| y=1] = 1 比较简单,我们能快速看出 E[X|y],而在稍微复杂的问题上,我们需要有一种找到 E[X|y] 的方法论 -- 有向图。用全期望公式求期望的过程可以用有向图的方式表示出来。以上述问题为例,我们可以画出下面的有向图。其中节点 0 表示未投掷出 6 的状态,节点 1 为表示掷出 6 的状态。0 为起点,1 为终点。图中的每条有向边有一个概率值,还有另一个权值,节点有一个期望值。我们要求的是从起始状态走向终点状态要走的步数的期望,每个节点有一个期望值表示从该节点走到终点节点的期望。由于 1 是终点,所以 E[1] = 0。0 是起点,我们要求的就是 E[0]。从节点 0 会伸出两条边,一条回到 0,表示当前的投掷没有掷出 6,另一条通向 1,表示当前的投掷掷出了 6。边上的概率值表示当前投掷的各个值的概率,而投掷一次步数加一,因此额外的边权都是 1。定义好这个图之后,我们就可以写出 E[0] 的表达式了E[0] = p * (1 + E[1]) + q * (1 + E[0])这个表达式就是全期望公式,只是将全期望公式中 E(X|y) 的部分用图中点权和边权的方式定义了计算方法。期望 DP通过以上的分析可以发现,当根据具体问题定义好有向图 D,起点 s,终点 t 后,我们就可以用类似于动态规划的方式求从 s 到 t 的某个指标的期望。状态定义 dp[u] := 从 u 到 t 的某个指标的期望 初始化 dp[t] 答案 dp[s] 状态转移 dp[u] = g(node[u], sum(p[u][v] * f(w[u][v], dp[v])))其中 f 是边权 w[u][v] (在上面的题中是 1) 和下一点的期望 dp[v] 的函数,g 是当前点的点权 node[u](在上面的题中没有)和 sum(p[u][v] * f) 的函数, 具体的函数形式取决于问题(在上面的题中 f 就是简单的 w[u][v] + dp[v], g 没有),需要具体分析。求解 dp[s] 的过程:对反图(将原图中的每条有向边的方向反转之后形成的图)进行拓扑排序,按照拓扑排序的顺序依次求解各个节点 u 的 dp[u],直至求解到 dp[s]。高斯消元当建出的有向图中有环的时候,求解 E[s] 的过程如果直接用期望 DP 从 dp[t] 向 dp[s] 推导的话是不行的,因为在推导 DP 状态时会出现类似于下面的情况(就是后面的【用户一次游戏带来的收入】那道题的转移方程,这里提前看一下)dp[0] = dp[1] dp[1] = 1 + 0.7 * dp[2] + 0.3 * dp[0] dp[2] = 2 + 0.6 * dp[3] + 0.4 * dp[1] dp[3] = 3 + 0.5 * dp[4] + 0.5 * dp[2] dp[4] = 0这种情况 dp 状态的转移形成了环,比如要求 dp[1] 要先知道 dp[2],要求 dp[2] 就要先知道 dp[1],没法求了。如果方程组是线性方程组,还有有办法的,解决办法是利用高斯消元的方式整体解这个线性方程组。关于高斯消元的推导、代码、题目,可以找相关资料学习。方法总结现总结求解这类期望问题的方法论。1、理论基础:全期望公式;2、处理具体问题时,我们先分析建图所需的信息:1)起点 s 和终点 t 状态是什么,还有哪些状态节点;2)求出各个状态节点一步可以走到哪些状态节点,概率又分别是多少;3)分析目标期望是否需要额外的点权和边权。3、建图,初始化 dp[t];4、分析状态转移方程中的 g,f 并写出实际的转移方程;5、求解 dp[s],这里注意两点:如果图没有环,直接按拓扑排序的顺序应用动态规划的方式推导;如果图中有环,还需进一步看方程组的形式,若是线性方程组我们可以用高斯消元法求解,对于其他形式方程这里暂不作讨论。题目: 装备升级下面开始今天要看的题目。题目描述如下:玩家的装备有从 0 到 4 这 5 个等级,每次升级都需要一个工序,一共有 0->1, 1->2, 2->3, 3->4 这 4 个工序,成功率分别为 0.9, 0.7, 0.6, 0.5。工序成功,玩家的装备就会升一级,但如果失败,玩家的装备就要倒退一级。例如玩家当前的等级为 2,目前执行 2->3 这个工序,如果成功,则装备等级从 2 升为 3,如果失败,装备等级就从 2 降到 1。问:玩家装备初始等级为 0, 升到 5 平均要经历多少个工序。思路参考步骤 1: 建图按照前面的方法总结。我们首先分析题目并建图,点状态是 0,终点状态是 4,此外还有 1, 2, 3 这三个中间状态。题目描述中明确给出了状态间的转移方向以及概率。我们要求的是从 s=0 走到 t=4 平均需要走的步数。由于每经过一条边都相当于走了一步,所以边上有额外的权 1。除此之外没有别的权,节点上也没有权。根据这些信息,我们首先把图建出来,如下步骤 2:期望DP期望 DP 的方程组如下:dp[0] = 0.9 * (1 + dp[1]) + 0.1 * (1 + dp[0]) dp[1] = 0.7 * (1 + dp[2]) + 0.3 * (1 + dp[0]) dp[2] = 0.6 * (1 + dp[3]) + 0.4 * (1 + dp[1]) dp[3] = 0.5 * (1 + dp[4]) + 0.5 * (1 + dp[2]) dp[4] = 0这是一个有环的图,且方程是线性方程组,面试的时候手推就可以。如果要编程的话,需要整理成标准形式后用高斯消元解决。步骤3 :高斯消元标准形式如下高斯消元求解#include <vector> #include <cmath> #include <iostream> #include <iomanip> using namespace std; int main() { // 系数矩阵 vector<vector<double>> c{{0.9, -0.9, 0, 0, 0} ,{-0.3, 1, -0.7, 0, 0} ,{0, -0.4, 1, -0.6, 0} ,{0, 0, -0.5, 1, -0.5} ,{0, 0, 0, 0, 1} }; // 常数列 vector<double> b{1, 1, 1, 1, 0}; int n = 5; const double EPS = 1e-10; // 高斯消元, 保证有唯一解 for(int i = 0; i < n; ++i) { // 找到 x[i] 的系数不为零的一个方程 for(int j = i; j < n ;++j) { if(fabs(c[j][i]) > EPS) { for(int k = 0; k < n; ++k) swap(c[i][k], c[j][k]); swap(b[i], b[j]); break; } } // 消去其它方程的 x[i] 的系数 for(int j = 0; j < n; ++j) { if(i == j) continue; double rate = c[j][i] / c[i][i]; for(int k = i; k < n; ++k) c[j][k] -= c[i][k] * rate; b[j] -= b[i] * rate; } } cout << std::fixed << std::setprecision(6); for(int i = 0; i < n; ++i) { cout << "dp[" << i << "] = " << b[i] / c[i][i] << endl; } }求解结果dp[0] = 10.888889 dp[1] = 9.777778 dp[2] = 7.873016 dp[3] = 4.936508 dp[4] = 0.000000蒙特卡洛模拟验证结果import numpy as np import bisect class Game: def __init__(self, p): self.n = p.shape[0] self.p = p for i in range(self.n): for j in range(1, self.p.shape[1]): self.p[i][j] += self.p[i][j - 1] def step(self, pos): return bisect.bisect_left(self.p[pos] ,np.random.rand()) def __call__(self): pos = 0 n_steps = 0 while pos != self.n: pos = self.step(pos) n_steps += 1 return n_steps p = np.array([[0.1, 0.9, 0.0, 0.0, 0.0] ,[0.3, 0.0, 0.7, 0.0, 0.0] ,[0.0, 0.4, 0.0, 0.6, 0.0] ,[0.0, 0.0, 0.5, 0.0, 0.5] ]) game = Game(p) def test(): T = int(1e7) total_n_steps = 0 for t in range(T): total_n_steps += game() print("Average Steps: {:.6f}".format(total_n_steps / T)) for i in range(10): test()模拟结果Average Steps: 10.890664 Average Steps: 10.887758 Average Steps: 10.882893 Average Steps: 10.889820 Average Steps: 10.891342 Average Steps: 10.888397 Average Steps: 10.888496 Average Steps: 10.891009 Average Steps: 10.890464 Average Steps: 10.8860804.6 一次游戏通关带来的收入在前一小节中我们通过【有向图+期望DP+高斯消元】这个方法论解决了装备问题,题目描述和解法可以参考前一小节。下面我们在装备升级问题上做一些变化,我们保持图结构不变,但是我们把期望 DP 转移方程中的边权改为节点的权。我们考虑一个在游戏相关岗位面试常见的问题: 一次游戏通关带来的收入一个游戏有四关,通过概率依次为0.9, 0.7, 0.6, 0.5。第一关不收费,第二到四关每次收费分别为1块, 2块, 3块。用户每玩一次都会无限打下去直至通关,通关后用户可以提现 10 块钱作为奖励。问: 公司可以在每次用户游戏中平均挣多少钱。思路参考我们首先考虑【公司可以在每次用户游戏中收费多少钱】,然后减去 10 块钱的奖励就是挣的钱。建图图与上面的装备升级那道题一样,只是边权没有了,节点有权重表示费用。期望 DP期望 DP 的方程组如下:dp[0] = dp[1] dp[1] = 1 + 0.7 * dp[2] + 0.3 * dp[0] dp[2] = 2 + 0.6 * dp[3] + 0.4 * dp[1] dp[3] = 3 + 0.5 * dp[4] + 0.5 * dp[2] dp[4] = 0这是一个有环的图,且方程是线性方程组,面试的时候手推就可以。如果要编程的话,需要整理成标准形式后用高斯消元解决。高斯消元标准形式如下高斯消元求解#include <vector> #include <cmath> #include <iostream> #include <iomanip> using namespace std; int main() { // 系数矩阵 vector<vector<double>> c{{1, -1, 0, 0, 0} ,{-0.3, 1, -0.7, 0, 0} ,{0, -0.4, 1, -0.6, 0} ,{0, 0, -0.5, 1, -0.5} ,{0, 0, 0, 0, 1} }; // 常数列 vector<double> b{0, 1, 2, 3, 0}; int n = 5; const double EPS = 1e-10; // 高斯消元, 保证有唯一解 for(int i = 0; i < n; ++i) { // 找到 x[i] 的系数不为零的一个方程 for(int j = i; j < n ;++j) { if(fabs(c[j][i]) > EPS) { for(int k = 0; k < n; ++k) swap(c[i][k], c[j][k]); swap(b[i], b[j]); break; } } // 消去其它方程的 x[i] 的系数 for(int j = 0; j < n; ++j) { if(i == j) continue; double rate = c[j][i] / c[i][i]; for(int k = i; k < n; ++k) c[j][k] -= c[i][k] * rate; b[j] -= b[i] * rate; } } cout << std::fixed << std::setprecision(6); for(int i = 0; i < n; ++i) { cout << "dp[" << i << "] = " << b[i] / c[i][i] << endl; } }求解结果dp[0] = 16.000000 dp[1] = 16.000000 dp[2] = 14.571429 dp[3] = 10.285714 dp[4] = 0.000000因此公司可以在每次用户游戏中收费 dp[0] = 16 块钱,减去用户通关的 10 块钱奖金,公司可以挣 6 块钱。蒙特卡洛模拟验证结果import numpy as np import bisect class Game: def __init__(self, transfer, cost): self.n = transfer.shape[0] self.cost = cost self.transfer = transfer for i in range(self.n): for j in range(1, self.transfer.shape[1]): self.transfer[i][j] += self.transfer[i][j - 1] def step(self, pos): return bisect.bisect_left(self.transfer[pos] ,np.random.rand()) def __call__(self): pos = 0 pay = 0 while pos != self.n: pay += self.cost[pos] pos = self.step(pos) pay += self.cost[self.n] return pay transfer = np.array([[0.1, 0.9, 0.0, 0.0, 0.0] ,[0.3, 0.0, 0.7, 0.0, 0.0] ,[0.0, 0.4, 0.0, 0.6, 0.0] ,[0.0, 0.0, 0.5, 0.0, 0.5] ]) cost = np.array([0.0, 1.0, 2.0, 3.0, -10.0]) game = Game(transfer, cost) def test(): T = int(1e5) total_pay = 0 for t in range(T): total_pay += game() print("Average income: {:.4f}".format(total_pay / T)) for i in range(10): test()模拟结果Average income: 5.9768 Average income: 6.0043 Average income: 5.9052 Average income: 6.0664 Average income: 6.0720 Average income: 5.9687 Average income: 6.0101 Average income: 6.0450 Average income: 5.9871 Average income: 6.07044.7祝你好运“祝你好运”一种赌博游戏,经常在嘉年华和赌场玩。玩家可以在 1, 2, 3, 4, 5, 6 中的某个数上下注。然后投掷 3 个骰子。如果玩家下注的数字在这三次投掷中出现了 x 次,则玩家获得 x 倍原始投注资金,并且原始投注资金也会返还,也就是总共拿回 (x + 1) 倍原始投注资金。如果下注的数字没有出现,则原始投注资金不会返还。问:每单位原始投注资金,玩家期望会输多少?思路参考设原始投注资金为 s,下注的数字在三次投掷中出现了 x 次,x 的取值为 0, 1, 2, 3,下面我们分别考虑这四种情况。按照期望的定义拿回的钱的期望为每单位投注资金的损失为 17/216 = 0.0787蒙特卡洛模拟import numpy as np def game(): x = 0 for _ in range(3): if np.random.randint(1, 7) == 1: x += 1 if x == 0: return 0 return x + 1 def test(): T = int(1e6) total_income = 0 for t in range(T): total_income += game() print("expected loss: {:.6f}".format((T - total_income) / T)) for i in range(10): test()模拟结果expected loss: 0.078817 expected loss: 0.078182 expected loss: 0.078731 expected loss: 0.078875 expected loss: 0.078526 expected loss: 0.078657 expected loss: 0.078634 expected loss: 0.079033 expected loss: 0.079353 expected loss: 0.079307

一道面试题,解锁JavaScript中的Event-Loop事件循环机制(面试必考)
面试题console.log('script start') async function async1() { await async2() console.log('async1 end') } async function async2() { console.log('async2 end') } async1() setTimeout(function () { console.log('setTimeout') }, 0) new Promise(resolve => { console.log('Promise') resolve() }) .then(function () { console.log('promise1') }) .then(function () { console.log('promise2') }) console.log('script end') 大家看看上面这道题!能告诉我输出结果是什么吗?如果你还不能确定,那么学完今天的内容,你就知道这道题该怎么做了!我们会在今天的结尾为大家揭示答案,及分析过程!那么,接下来就开始我们今天的学习吧!一、进程和线程1、什么是进程?在计算机科学中,进程是指在系统中运行的程序的实例。它是计算机中的一个独立执行单元,包含了程序代码、数据以及程序的执行状态。每个进程都有其独立的内存空间,不同进程之间一般是相互隔离的,这样可以确保它们不会直接干扰彼此的执行。就好比你打开电脑的任务管理器,你看到一个又一个的程序就是你的电脑的进程!也可以理解为CPU运行指令和保存上下文所需的时间!2、什么是线程?进程中的更小的单位,描述了一段指令执行所需的时间,在计算机科学中,线程是指进程内的一个执行单元,它包含了执行程序的代码和相关的上下文信息。线程是进程的一部分,一个进程可以包含多个线程,这些线程共享相同的资源,如内存空间和文件句柄。线程之间的切换相对于进程切换来说更加轻量级,因为它们共享相同的地址空间。有一点我们要记住一点JS引擎线程和浏览器的渲染进程是互斥的,会造成不安全的渲染!面试题:打开一个tap页面,输入一个url回车到页面展现的过程。问:这中间发生了什么?我们新开的一个页面,就是新开一个进程,需要多个线程配合才能完成页面的展示,其中有很多的细节,大致可以分为三步:1、渲染线程(GPU) ,2、http请求进程,3、JS引擎线程**什么是GPU?**GUP它是属于浏览器的,具有绘制功能,将页面上的展示绘制出来!就比如找到哪个物理发光点 应该亮什么灯,我们可以把它理解成一个画笔,作用就是把整个页面画出来。3、JS是单线程JavaScript语言是一门单线程语言,这意味着,在同一时间只能执行一个任务。正是因为JS单线程的这种特点,给它带来一些优点!简单性: 单线程模型使得编写和调试JavaScript代码相对简单。开发人员不必担心多线程的同步和竞态条件问题,这降低了代码的复杂性。避免竞态条件: 多线程环境下,如果没有正确处理同步,可能会导致竞态条件(Race Condition)。单线程避免了这种情况,因为在任何给定时刻只有一个任务在执行。前端开发中的简便性: 在Web开发中,JavaScript主要用于操作DOM、处理用户交互等,这些任务通常不需要多线程。单线程的模型对于处理这些任务是足够的,并且简化了前端开发的复杂性。更好的可控性: 单线程模型使得代码的执行流程更加可控。事件循环(Event Loop)机制确保任务按照特定的顺序执行,使得开发者更容易理解代码的执行流程。资源节省: 单线程模型节省了在多线程切换上的一些开销。在多线程环境中,线程的切换可能会引入额外的开销,而单线程模型避免了这个问题。避免死锁: 多线程应用程序中,死锁是一个潜在的问题,可能会导致程序无响应。单线程模型避免了这个问题,因为只有一个执行线程。但是,在某些情况下,也可能导致性能问题,特别是在处理大量计算或需要等待的异步任务时。为了克服这些限制,JavaScript使用异步编程模型,允许开发者利用单线程的同时,通过异步操作提高并发性。有人就要问了,为什么JS不设计成多线程的?这是因为JS设计之初是打算设计成浏览器的脚本语言,多线程 高并发高功耗,多线程运行的时候会多占一些内存,是为了遏制同时干多个事情的能力,节约进程内存的开销。接下来,我们就为大家介绍JavaScript中异步编程!二、JavaScript中的异步JavaScript异步编程中存在两个概念!宏任务和微任务!1、宏任务(异步很凶的代码)整体的代码(Script): 整体的 JavaScript 代码作为一个宏任务执行。定时器事件(setTimeout、setInterval): 设置定时器的回调函数会作为一个宏任务执行。事件监听器(Event Listeners): 通过事件监听器绑定的回调函数会作为宏任务执行。I/O 操作: 执行某些 I/O 操作时,例如文件读写、网络请求,会作为宏任务执行。用户交互事件: 用户的交互事件,例如点击、键盘输入,会触发相应的回调函数,作为宏任务执行。UI-rendering: 页面渲染 重要2、微任务Promise 的回调函数: Promise 的 then 和 catch 方法中的回调函数会作为微任务执行。MutationObserver: 使用 MutationObserver 监听 DOM 变化的回调函数会作为微任务执行。process.nextTick(Node.js): 在 Node.js 中,process.nextTick 中的回调函数会作为微任务执行。queueMicrotask: queueMicrotask 函数添加的回调函数会作为微任务执行。Object.observe(已废弃): Object.observe 中的回调函数会作为微任务执行(注意:Object.observe 已被废弃)。我们了解这两个概念有什么用呢?接下来就介绍我们今天的应用!三、事件循环机制event-loop 面试必考事件循环机制的步骤如下:执行同步代码(这属于宏任务)当执行栈为空,去查询是否有异步代码需要执行执行微任务如果有需要,会渲染页面d执行宏任务,(这也叫下一轮的event-loop的开启)接下来我们看看这个案例:let a = 1 console.log(a); // v8决定定时器耗时 setTimeout(()=>{ console.log(a); },1000) let b = 2 //for循环是由我们CPU说要耗时的,v8眼里它是不耗时的 for(let i = 0;i<10000;i++) { console.log(b); } 在这个案例当中,在浏览器引擎眼里,for循环是不会耗时的,但是我们的CPU运行需要耗时,我们假设for执行完需要1s,那么,我们的setTimeout需要耗时多少?答案是2s!for执行完的时间由我们的CPU决定,它是一个同步代码。你也许会疑惑,这里也没有用到事件循环机制阿?对!那我们用事件循环机制来分析下面这个案例console.log('starting'); setTimeout(()=>{ console.log('settimeout'); setTimeout(()=>{ console.log('inner'); }) console.log('end'); },1000) new Promise((resolve,reject)=>{ console.log('promise'); resolve() }) .then(()=>{ console.log('then'); }) .then(()=>{ console.log('then2'); }) .then(()=>{ console.log('then3'); }) 这个案例的执行结果是什么?我们后台数据存在两个队列!我们开始分析这个案例:浏览器从上往下执行,根据事件循环机制的步骤:第一轮事件循环机制一、执行同步代码 1、第一行console.log('starting');这是一个同步代码,直接执行! 2、往下是第一个定时器,我们把它推入到宏任务队列:宏任务队列:setTimeout 3、然后来到new Promise这个语句是一个同步代码,我们执行它的内部:console.log('promise');同步代码,输出promise,执行 resolve() 4、第一个.then,这个是微任务,推入到微任务队列当中(我们用数字[1]表示先后,区别相同的标签):微任务队列:.then()[1] 5、第二个.then,这个是微任务,推入到微任务队列当中:微任务队列:.then()[2],.then()[1] 6、第三个.then,这个是微任务,推入到微任务队列当中:微任务队列:.then()[3],.then()[2],.then()[1]二、当执行栈为空,去查询是否有异步代码需要执行(意思就是如果没有同步代码需要执行了,就接着往下走)三、执行微任务 1、获取微任务队列微任务队列:.then()[3],.then()[2],.then()[1],我们知道队列是先进先出,所以我们从头开始执行! 2、执行.then()[1],输出then此时微任务队列:微任务队列:.then()[3],.then()[2] 3、执行.then()[2],输出then2此时微任务队列:微任务队列:.then()[3] 4、执行.then()[3],输出then3此时微任务队列:微任务队列:四、如果有需要,会渲染页面d五、执行宏任务,(这也叫下一轮的event-loop的开启) 1、获取宏任务队列:宏任务队列:setTimeout 2、新的一轮事件循环:宏任务队列:第二轮事件循环一、执行同步代码 1、console.log('settimeout');同步代码直接输出settimeout 2、setTimeout,推入宏任务队列:宏任务队列:setTimeout 3、console.log('end');同步代码直接输出end二、当执行栈为空,去查询是否有异步代码需要执行(意思就是如果没有同步代码需要执行了,就接着往下走)三、执行微任务(没有微任务)四、如果有需要,会渲染页面d五、执行宏任务,(这也叫下一轮的event-loop的开启) 1、获取宏任务队列:宏任务队列:setTimeout 2、新的一轮事件循环:宏任务队列:第三轮事件循环一、执行同步代码 1、console.log('inner');同步代码,直接输出:inner由于没有代码了,后续内容结束!总结下来,我们的输出应该是:输出: starting promise then then2 then3 settimeout end inner 没错!这就是我们想要的答案!值得注意的是!我们看看这个案例:function a(){ setTimeout(()=>{ console.log('a'); },1000) } function b(){ setTimeout(()=>{ console.log('b'); },500) } a() b() //输出: //b //a 为什么这里是先输出b再输出a呢?这是因为setTimeout定时器比较特殊,在宏队列中,几乎是同步执行,时间短的先输出,像上述案例,执行完成总共花费的时间为1s好了学到这里,你也就基本懂了事件循环机制,接下来为解决我们今天的"大人物"面试题而准备吧!async函数我们参考MDN官方文档介绍!async 函数 - JavaScript | MDN (mozilla.org)async 函数是使用async关键字声明的函数。async 函数是 AsyncFunction 构造函数的实例,并且其中允许使用 await 关键字。async 和 await 关键字让我们可以用一种更简洁的方式写出基于 Promise 的异步行为,而无需刻意地链式调用 promise。返回值一个 Promise,这个 promise 要么会通过一个由 async 函数返回的值被解决,要么会通过一个从 async 函数中抛出的(或其中没有被捕获到的)异常被拒绝。描述async 函数可能包含 0 个或者多个 await 表达式。await 表达式会暂停整个 async 函数的执行进程并出让其控制权,只有当其等待的基于 promise 的异步操作被兑现或被拒绝之后才会恢复进程。promise 的解决值会被当作该 await 表达式的返回值。使用 async/await 关键字就可以在异步代码中使用普通的 try/catch 代码块。备注: await关键字只在 async 函数内有效。如果你在 async 函数体之外使用它,就会抛出语法错误 SyntaxError 。备注: async/await的目的为了简化使用基于 promise 的 API 时所需的语法。async/await 的行为就好像搭配使用了生成器和 promise。async 函数一定会返回一个 promise 对象。如果一个 async 函数的返回值看起来不是 promise,那么它将会被隐式地包装在一个 promise 中。面试题答案console.log('script start') async function async1() { await async2() console.log('async1 end') } async function async2() { console.log('async2 end') } async1() setTimeout(function () { console.log('setTimeout') }, 0) new Promise(resolve => { console.log('Promise') resolve() }) .then(function () { console.log('promise1') }) .then(function () { console.log('promise2') }) console.log('script end') 我们拿到面试题,然后进行分析。根据我们的事件循环机制进行分析!第一轮事件循环一、执行同步代码 1、console.log('script start')同步代码,直接输出script start 2、async函数async1和async2的声明,不用管 3、async1()函数调用,进入函数内部,碰到await async2(),原本我们会把它和它之后当作微任务放入微任务队列当中,现在它只会把它后面的代码放入微任务当中,而它接着的代码,被强行提前!所以这里会先调用async2,而console.log('async1 end')进入微任务队列:微任务队列:console.log('async1 end'),而async2()会立即调用 4、紧接着第三步,进入async2函数,里面没有awiat,console.log('async2 end')为同步代码,输出async2 end 5、跳出函数调用,往下执行,一个定时器的声明。加入宏任务:宏任务队列:setTimeout[1] 6、new promise为同步代码,执行里面的逻辑,console.log('Promise')输出Promise,再调用resolve() 7、.then,推入微任务队列:微任务队列:.then[1],console.log('async1 end') 8、又一个.then,推入微任务队列:微任务队列:.then[2],.then[1],console.log('async1 end') 9、console.log('script end')同步代码,输出script end二、当执行栈为空,去查询是否有异步代码需要执行(意思就是如果没有同步代码需要执行了,就接着往下走)三、执行微任务 1、获取微任务队列:微任务队列:.then[2],.then[1],console.log('async1 end') 2、开始执行,输出async1 end然后微任务队列:.then[2],.then[1] 3、再执行.then[1],内部有一个输出代码console.log('promise1')输出promise1此时微任务队列:.then[2] 4、执行.then[2],内部有一个输出console.log('promise2'),输出promise2,此时微任务队列:四、如果有需要,会渲染页面d五、执行宏任务,(这也叫下一轮的event-loop的开启) 1、获取宏任务队列:宏任务队列:setTimeout 2、新的一轮事件循环:宏任务队列:第二轮事件循环 d定时器当中只有一个console.log('setTimeout')直接输出setTimeout输出结束!!最终结果为:输出: script start async2 end Promise script end async1 end promise1 promise2 setTimeout 这样,我们这道面试题就解决啦!!是不是很简单呢?不知道大家,看完这篇文章有没有对事件循环机制有一定的理解!希望大家看完能够有所收获!

【爆肝干货】面试官:你能实现一下call()的源码嘛?今天我们就来搞懂call()源码instanc
前言面试官:你能实现一下call()源码吗?轻松学习JavaScript类型转换和call()方法源码以及instanceof源码又来上干货啦!!今天我们来学习JavaScript中的类型转换和call方法源码!正文JavaScript中的数据类型要进入今天的学习,我们要先总结一下JavaScript中各种数据类型!基本数据类型 let str = 'hello'//string let str2 = 'hello'//string let num = 12//number let flag = false //boolean let und = undefined //undefined let nul = null //null let big = 1232n //big integer big number用于存2**53-1以下或者2**-53以上 let s = Symbol('hello') //Symbol不参与逻辑运算 引用数据类型 let obj = {}//对象 let arr = []//数组 let fn = function (){}//函数 let date = new Date()//时间 let regex = /模式/;//正则 以上就是我们目前总结的数据类型!这其中,我觉得有必要介绍的就是big integer和symbol了对于big integer,顾名思义,它就是大数的代表!一般我们的数字只能运算2^-53~2^53-1之间的数据,而大数,则是用来运算超过这段区间的值!解决大家遇见的精度问题!相信大家很容易理解!对于symbol呢,每个通过 Symbol 创建的值都是唯一的,即使创建时使用相同的描述字符串。这也是为了避免我们在项目当中遇到与其他人变量名相同,导致功能出现问题的情况,每一个由Symbol创建的值都是独一无二的!接下来,我们步入我们的正题!数据类型判断typeof()方法再JavaScript中,typeof()方法目前能判断所有的基本数据类型,null除外,这是一个历史遗留问题,我们稍后介绍。对于引用类型,typeof()方法又不那么管用了,它会将所有的引用类型都判断为Object,但是函数除外,typeof能判断出函数的数据类型,这里为什么,我们不予探究!接下来,我们就应用上述的变量,来使用tyoeof()方法进行判断!基本数据类型console.log(typeof(str));//typeof str 能得到String console.log(typeof(num));//number console.log(typeof(flag));//boolean console.log(typeof(und));//undefined console.log(typeof(nul));//object这是js中的bug,历史遗留问题 console.log(typeof(big));//bigint console.log(typeof(s));//symbol 输出: string number boolean undefined object bigint symbol 可以看到,typeof()方法能够判断所有的基本数据类型!但是null除外!为什么呢?那我们就要聊聊当初设计师们是如何设计出typeof()方法。在当时,设计typeof()方法的时候,设计师们想到利用二进制的前三位进行判断,只要前三位为0,就都是Object因为,基本数据类型大多前三位有值嘛,于是得到所有人的一致认可!但是,之后设计null的时候,设计师们觉得啊,既然是空值,那null的二进制就全是0好了,就此,人们就发现typeof()判断null值的时候!就是产生了一个BUG,会被识别为Object,时至今日,这个BUG仍然存在!听了上面所说!你也就知道了typeof()判断引用类型的结果!我们来看看!引用类型console.log(typeof(obj));//object console.log(typeof(arr));//object 在typeof的眼里,所以引用类型都是对象object 判断不了引用类型 console.log(typeof(fn));//function typeof只能判断function console.log(typeof(date));//object console.log(typeof(regex));//object 输出: console.log(typeof(obj));//object console.log(typeof(arr));//object 在typeof的眼里,所以引用类型都是对象object 判断不了引用类型 console.log(typeof(fn));//function typeof只能判断function console.log(typeof(date));//object console.log(typeof(regex));//object 对于引用类型,typeof()又只能准确判断function,这点大家记住就好!你又问了!不是能判断Object嘛?这一点,你可以认为它能判断Object也能认为是运气好,刚好撞中了!小编就以后者为主了!这一点仁者见仁智者见智!大家不必纠结!介绍到这里,大家稍加理解,就能学会typeof()这个方法的原理了,接下来我们开始介绍另外一种方法!instanceof判断在 JavaScript 中,instanceof 是一个运算符,用于检测对象是否是某个构造函数(或者其原型链上)的实例。instanceof会顺着隐式原型往上找,直到找到了 obj.__proto__===Object.prototype一步步找obj.__proto__.__proto__===Object.prototype一步一步下去,找到了就返回true,没有找到就返回false它的语法如下:object instanceof constructor object:要检测的对象。constructor:要检测的构造函数(类型)。instanceof 运算符返回一个布尔值,如果 object 是 constructor 的实例,返回 true;否则返回 false。instanceof 运算符的原理是这样:检查对象的原型链: instanceof 首先检查对象(左操作数)的 [[Prototype]] 链,即原型链,原型链大家可以参考:【面试】网易:所有的对象最终都会继承自Object.prototype 吗?搞懂原型原来这么简单!! - 掘金 (juejin.cn)匹配构造函数的原型: 然后,它检查构造函数的 prototype 属性是否出现在对象的原型链上的任何位置(会顺着原型链不断查找)。返回布尔值: 如果找到匹配,instanceof 返回 true,表示对象是构造函数的实例。如果在整个原型链上都找不到匹配,返回 false。我们来几个案例分析一下:代码中仍然是拿到刚刚的变量。console.log(obj instanceof Object);//判断obj是不是隶属于object console.log(arr instanceof Array); console.log(fn instanceof Function); console.log(date instanceof Date); console.log(str instanceof String);//判断不了原始类型 输出 true true true true false 我们会发现instanceof判断不了原始数据类型,这是为什么呢?这是因为instanceof 是基于对象的原型链进行检查的。原始数据类型(例如字符串、数字、布尔等)并不是对象,它们没有自己的原型链,因此无法通过 instanceof 来判断。instanceof 主要用于检查对象是否是某个构造函数的实例,它在检查原型链时查找构造函数的 prototype 属性。对于原始数据类型,因为它们不是对象,也就没有 prototype 属性,所以 instanceof 检查不会得到期望的结果。当然,它能不能判断数组是不是对象呢?能!console.log(arr instanceof Object) 输出:true 为什么能?因为所有的引用类型都有一个共同的祖先Object,所有instanceof在寻找原型链中,也一定能找到这样一个Objcet的protype属性,所以也能判断!接下来,我们可以实现一下instanceof的源码来帮助我们更好地理解:function instanceOF(L,R){ let left = L.__proto__ let right = R.prototype while(left!=null){ if(left===right){return true} left = left.__proto__ } return false } 以上就我们写的源码了!我们具体是如何实现的呢?其实就是根据instanceof的原理:一步一步查找原型链进行判断。所以,在我们自己写的源码当中,我们写了一个函数这个函数有两个参数L代表我们要判断的对象,R代表我们要判断的类型我们在函数体当中定义两个变量left和rightleft指向我们要判断的对象的隐式原型__proto__,right指向判断类型的构造函数的原型prototype接下来,我们定义一个while循环,当我们的left不为null持续循环。在循环体当中判断left是否等于right,是则返回true,不是,则让我们的left顺着原型链往下找!直到找到匹配的值返回true或者没找到直到null结束循环则返回false这就是我们构造函数的编写原理了,让我们来验证一下是否成功!console.log(instanceOF([],Object)) console.log(instanceOF('',Array)) 输出: true false 经过多个案例,也是证明,我们写的函数没有问题!到这里,大家好好理解一下,也就能搞懂了!接下来是:Array.isArray()Array.isArray(arr)函数自带的方法,用于判断是否为数组,数组独有。只能判断是否为数组!console.log(Array.isArray([])); console.log(Array.isArray({})); 输出: true false 这个我们一笔带过就好啦!接下来是我们的重头戏!Object.prototype.toString.call(xxx)这里,我们就不得不提一嘴!Object.prototype.toString()这个方法在官方文档Annotated ES5 :Annotated ES5是这样介绍的:什么?你看不懂?来翻译一下!1.如果this值为undefined,返回"[object Undefined]"2.如果this值为null,返回"[object Null]"3.将O作为ToObject(this)的执行结果.4.让class成为 O 内部属性[[Class]]的值5.返回由三个字符串"[object "、 class 和 "]" 三部分拼接而成的字符串什么,中文也看不明白?那我们之间上案例好了!console.log(Object.prototype.toString('12'))输出 [object Object]咦,这也不对啊?这是因为toString()不接收值,也就意味其中你输入任何参数都没效果,toString的运行原理就是,先判断调用它的那个变量的类型,然后再把它转换为字符串!Object.prototype.toString()输出[object Object]是官方规定的输出类型,我们不做过多探究!在官方文档的解释,其实我们可以通过修改String函数this的指向来改变它的结果!于是Object.prototype.toString.call(xxx)方法应运而生!我们来看看这个案例:console.log( Object.prototype.toString.call(123) ); console.log( Object.prototype.toString.call('dd') ); console.log( Object.prototype.toString.call(undefined) ); console.log( Object.prototype.toString.call(null) ); console.log( Object.prototype.toString.call({}) ); console.log( Object.prototype.toString.call([]) ); console.log( Object.prototype.toString.call(new Date()) ); console.log( Object.prototype.toString.call(function() {}) ); 输出: [object Number] [object String] [object Undefined] [object Null] [object Object] [object Array] [object Date] [object Function] 我们就找到了一个能够判断所有数据类型的方法!但是!这样的输出结果,可能并不是我们想要的?于是我们可以通过这样一个操作来实现!只获取它的数据类型!function isType(s) { return Object.prototype.toString.call(s).slice(8,-1) }; console.log(isType('1455')) 输出:String 这样,就获取了我们想要的结果!其中slice(8,-1)表示从下标8开始到倒数第二个!-1表示的是到倒数第二个结束!其中null和undefined为什么会输出null和undefined其实这是官方定死的,我们不用过多纠结!为什么我们在Object.prototype.toString()加一个.call就能达到这样的效果呢???根据官方文档,我们可以通过修改this的值,来改变对应的输出结果!我们要如何去改变this的值呢?这里我已经在前文介绍过:OpenAI见了也皱眉?JS的this关键字,十分钟带你跨过大山! - 掘金 (juejin.cn)大家可以前往学习一下,我们在这里利用的就是call()达成显式绑定的条件来改变this的指向从而达到我们的目标!但是其实,call()利用的还是隐式绑定来达成效果的!接下来就来到了我们今天的重头戏!call()源码的实现!我们先解释一下call()的原理!call()的原理 { fn:foo } obj.fn() delete obj.fn() 上面其实就是对call()原理的解释,我们来口头描述一下!call()方法其实就通过现在先把前面的函数体挂在它传入的对象当中! 然后立马用这个对象调用这个函数体。(这里其实就利用隐式绑定修改this的指向) 紧接着又偷偷摸摸给你把对象当中的这个函数体给删除掉!我们要实现这样一个call()源码!,我们按照这个思路来写即可!我们就先上源代码!Function.prototype.myCall = function(context){ //this是这个函数,隐式绑定 if(typeof this!='function'){//或者条件里面写this instanceof Function throw new TypeError('myCall is not a function')//效果和return一样,后续的逻辑不会执行 } //获取实参 //类数组不能用数组的方法,只要下标 //Array.from(类数组)把类数组转成一个数组 // let arge = Array.from(arguments).slice(1)//从下标一 切完最后所有,不影响原数组,得到新数组, let arge = [...arguments].slice(1) context.fn = this console.log(this); //let res = context.fn(...arge)如果有返回值,就return res let res = context.fn(...arge)//触发隐式绑定规则 会给对象赛东西 (...arge)结构数组 delete context.fn return res } foo.myCall(obj,1,2); 这其实就是一个call()源码了!我们来给大家分析一下!注意这里的点例如:foo.myCall(obj,1,2); 我们所说的函数体就是foo,我们所说的对象就是obj,1,2就是传的实参! arguments 所有的函数都有这个关键字 是所有参数的统称 它是一个类数组我们定义了一个myCall函数,其中传入一个形参context在函数体内,我们第一步用this判断调用myCall的数据是不是一个函数!如果不是则抛出一个typeError的异常!因为我们调用call方法的必须是一个函数,通过修改函数里面的this所以我们要加上一个这样的判断!紧接着!我们用一个变量let arge = [...arguments].slice(1)来存储函数当中可能传过来的实参!因为我们无法确定函数体是否有实参传过来!arguments是一个类数组,我们用一个新的参数let arge = [...arguments].slice(1) 这样,我们会先解构类数组,再把解构后的一个元素去掉,存入到arge中。接下来,我们用context.fn = this这个相当于在对象添加一个属性,属性名为fn,值为调用myCall函数体,这样就在对象中引用了这个函数体,形成了一个隐式绑定!然后我们let res = context.fn(...arge)这里相当于对函数体进行调用!触发隐式绑定规则,同时给对象赛(...arge)结构数组然后我们再把这个对象当中引用的函数体删除掉!最后返回传过来的实参!接下来我们验证一下!!var obj = { a:1, } function foo(num1,num2){ console.log(this.a,num1,num2); } Function.prototype.myCall = function(context){ if(typeof this!='function'){ throw new TypeError('myCall is not a function') } let arge = [...arguments].slice(1) context.fn = this let res = context.fn(...arge) delete context.fn return res } foo.myCall(obj,1,2); 输出: 1 1 2 输出结果没有问题!!这样我们就实现了call()方法的源码!!

面试题:[]==![] 结果是true or false ?带大家彻底搞懂JS类型转换机制 通宵爆肝
面试题大家来看看这样一道面试题吧!它的结果是什么?[]==![] //输出?true or false?就这?一个这样的代码?小心有坑!大家好好思考一下!我们学完今天的内容,带大家彻底学习完JavaScript中的类型转换!再最后为大家解答就会一目了然!数据类型的转换基本数据类型的转换在JavaScript中,类型转换是指将一个值从一种类型转换为另一种类型。我们知道,JavaScript分为两大数据类型!原始数据类型和引用类型我们先讲原始数据类型的转换,因为null和undefined是特殊的原始值,它们没有构造函数,不能像对象一样使用构造函数来创建他们的实例,所以我们不予研究。ES6也新增了Symbol和BigInt,其实他们类型转换基本机制相同,我们也不予研究。我们在基本数据类型的转换当中研究:原始值转布尔值原始值转数字原始值转字符串原始值转对象其实原始类型的转换还是很简单的,用一张图就可以为大家总结!但是不用着急,我们一步一步走下去,带大家彻底理解!!一、原始值转布尔值// 原始值转布尔值 console.log(Boolean(false));//false // 直接调用Boolean()是false console.log(Boolean());//false console.log(Boolean(1));//true console.log(Boolean(0));//false console.log(Boolean(-1));//true console.log(Boolean(undefined));//false console.log(Boolean(null));//false console.log(Boolean('123'));//true console.log(Boolean(''));//false console.log(Boolean('0'));//true console.log(Boolean(NaN));//false 布尔类型转布尔类型是不变的,为什么,我们接着往下看。注意一点!如果我们直接调用Boolean()会返回默认的一个结果false,'',null和undefined和NaN和0和'false'传过来为什么是false呢?这是因为在 JavaScript 中,有一些值被视为“假值”(falsy values),它们在布尔上下文中被当作 false。以下值在布尔上下文当中都会被当作为false undefined null 0 ""(空字符串) false NaN 对于NaN 和undefined 和 null 和 ""(空字符串),我觉得他们可以理解为直接调用Boolean()所以传过来的结果是false其他所有值都被视为“真值”,我们知道在布尔类型当中,0为false,其他值为true,所以对于其他有值的原始值,结果就都是true二、原始值转数字console.log(Number());//0 console.log(Number('123'));//123 console.log(Number('hello'));//NaN 属于Number 代表无法表示的值 console.log(Number(undefined));//NaN console.log(Number(null));//0 console.log(Number(true));//1 console.log(Number(false));//0 console.log(Number('12.14'));//12.14 console.log(Number('0012.14'));//12.14 console.log(Number('-12'));//-12 //字符串是数字就能转,还是是其他的字符串就不能转 与布尔一样,我们直接调用Number()会默认返回地返回0对于字符串,如果字符串当中夹杂了非组成数字的字符,那么这个字符串,无法转换为数字,而会被识别为NaN Not a Number大家不用担心-和.的问题,这些可以组成数字的字符是能够被Number识别并且转换的,如果是0014这种,它也会贴心地把前面的两个0去掉,给你转换为14。而undefined的这个原始值,会被识别为NaN,而null会被识别为0,同样,布尔值转数字的话true为1,false为0。三、原始值转字符串//new String不传实参是一个空对象 console.log(String());//'' console.log(String(123));//'123' console.log(String(NaN));//NaN console.log(String(Infinity));//Infinity console.log(String(undefined));//undefined console.log(String(null));//null console.log(String(true));//true 而原始值转字符串就相当简单粗暴了!直接将内容转换为字符串,不讲道理!值得注意的就是,new String不传实参其实就是一个空对象!四、原始值转对象console.log(Object());//{} console.log(Object('123'));//[String: '123'] console.log(Object(123));//[Number: 123] console.log(Object(null));//{} console.log(Object(undefined));//{} console.log(Object(true));//[Boolean: true] console.log(Object(NaN));//[Number: NaN] 直接调用Object构造函数,也是返回一个空对象,而除了null和undefined,会转为空对象除外,对于其他的原始值都会成功地转为对象!同时,我们其实还可以这样转对象:console.log(new Number(123));//[Number: 123] 没有,Object(123)和new Number(123)有异曲同工之妙!引用类型的转换引用类型的转换就有很多条条框框了,但是我们不用着急,我们来一步一步稳扎稳打,不踩坑!一、对象转数字传统方法console.log(Number({}));//NaN console.log(Number([]));//0通过传统的办法,调用Number直接转换也是可以的。我们可以看到对象转换为数字是NaN,而空数组转换为数字是0(这里我个人认为传的一个空数组,相当于直接调用Number()不传实参一个意思。)ValueOf()方法JavaScript 中的 valueOf() 方法用于返回指定对象的原始值,若对象没有原始值,则将返回对象本身。valueOf()方法,但是值得注意的是:valueOf()只对包装类有用!也就是通过new实现的对象!Array:返回数组对象本身。Boolean: 返回布尔值Date:存储的时间是从 1970 年 1 月 1 日午夜开始计的毫秒数 UTC。Function: 返回函数本身。Number: 返回数字值。Object:返回对象本身。这是默认情况。String:返回字符串值。Math 和 Error 对象没有 valueOf 方法。//valueOf只对包装类有用 let a = new String('123') console.log(a.valueOf());//123 let b = new Number(123); console.log(b.valueOf());//123 官方文档对Object.valueOf()当然官方文档可是纯英文的!在这里强烈给大家安利一个Chrom小插件--沉浸式翻译!👍👍👍用过的都说好用!很可惜,这里阿远也是么有看懂😳😳,大家先知道这个方法!待阿远闭关修炼一波~~🏄官方也对To Number也有描述二、对象转字符串对象转字符串let a = {} console.log(String({}));//[object Object] console.log(a.toString());//[object Object] console.log(Object.prototype.toString.call(a))//[object Object] 我们可以通过三种方式String和a.toString()和Object.prototype.toString.call(a)第一种StringString其实就是new String,将对象强制转换伪字符串值!我们来给大家看看官方文档(地址阿远也给大家丢这里了哈!):es5.github.io/#x4.3.16第二种toString在JavaScript中,toString是一个用于将对象转换为字符串的方法。toString方法会先对传进来的值进行分类,如果是对象,就会转换为字符串,再返回。我们来看看官方文档的描述(es5.github.io/#x9.8):第三种Object.prototype.toString.call(a)这个方法在之前的文章:【爆肝干货】面试官:你能实现一下call()的源码嘛?今天我们就来搞懂call()源码instanceof源码和类型判断 - 掘金 (juejin.cn)中介绍过!这个就是利用了原型链上Object的原型上toString,再利用call将this显式绑定在a对象上,能够实现一个转字符串的效果,我们来看看官方文档(es5.github.io/#x9.8):数组转字符串我们再来说一下数组转字符串let arr = [1,1,2,2] // 数组调用toString是把数组里面的元素全部拿出来,以逗号进行拼接成一个字符串! console.log(arr.toString())//输出:1,1,2,2 上文我们就介绍过,toString方法会先对传进来的值进行一下类型判断。如果是数组的话,那么toString方法,会把数组当中的每一个元素,一个一个拿出来,用,拼接成字符串!时间转字符串时间转字符串我们也要知道!let time = new Date() console.log(time.toString());//Tue Nov 21 2023 19:53:24 GMT+0800 (GMT+08:00) 同样,我们也可以通过toString方法可以将时间转换为字符串函数转字符串函数转字符串也是调用toString方法var fn = function () {} console.log(fn.toString());//function () {} 对象转布尔值所有对象转布尔值都是true,我们记住这一点就好了!ToPrimitive()重要重要重要!!!我们可以看到,在Number和toString的官方文档当中有一个ToPrimitive方法,我们先为大家解释!这个方法是我们JavaScript内置引擎自己调用的方法!Number的调用!首先,如果传入的参数是一个基本类型的话,那么会直接返回!如果不是的话,会首先使用valueOf方法转换,如果得到原始值,则返回,如果得不到,那么会调用toString方法,如果得到原始值,则返回,如果还不行的话,就会报错了!总结一下Number的调用!ToPrimitive(obj,Number) ===> Number({}) 先用valueOf转换 发现转换不了,用toString 成功,再就是原始值转原始值js自己调用的方法如果obj是基本类型,直接返回否则,调用valueOf 方法,如果得到原始值,则返回否则, 调用toString方法,如果得到原始值,则返回否则, 报错toString的调用在我们使用toString的时候,我们的内核引擎会先判断,传进来的值如果是基本类型,也是直接返回,否则会先调用toString方法,如果能得到原始值,就返回(注意!如果我们重写了toString方法,会导致访问不到Object下的toString方法),如果还不行,就会调用valueOf方法,如果得到原始值,则返回!如果还不行就会报错!总结toString的调用ToPrimitive(obj,toString) ===> String({})如果obj是基本类型,直接返回否则,调用 toString 方法,如果得到原始值,则返回 (假如,你再这个数据上重写了toString方法,导致访问不到Objcet下的toString方法)否则, 调用 valueOf 方法,如果得到原始值,则返回否则, 报错我们再来看看官方文档!其实,大家用着就会发现,似乎我们每次判断都不会用上valueOf,那为什么这个步骤当中会加上valueOf呢?前面已经说了,valueOf是可以转换包装类,之所以在Toprimitive中加入valueOf就是应对万一转换的就是包装类的情况,有时候,toString方法也可能会在本数据当中被重写,也可以应对这种情况运算符JavaScript中有各种运算符,我们再简单学习一下一些运算符!一元运算符一元运算符有很多种例如:delete,?,!,+,-等等我们来一个案例学习一下+console.log(+'1');//输出:1 console.log(+[]);//输出:0 console.log(+{});//输出:NaN 一元运算符+会将操作数转换为Number类型也就是说,上面案例当中的所有类型都要转换为Number字符串转数字'1'可以直接转换为1,而[]和{}不同,我们来分析一下,转数字,用第一个步骤调用toPrimitive!首先,第一步不是基本类型,走第二步,使用valueOf,我们在上文说过valueOf对包装类才有效,是通过返回this本身。什么是包装类?"包装类"的概念通常与Java中的含义类似,也是用于将基本数据类型封装成对象new Number() new String() new Boolean() 于是,我们的引擎会调用toString方法,[]变为字符串之后为一个''空字符串,再去调用toNumber于是得到了0,我们可以参考上文官方文档的Number转换,同理,对于{}要转换为数字,也是第一步走toPrimitive方法,第一条和第二条都走不通!走toString方法,对象调用toString方法我们知道结果为:[object Object],再调用toNumber于是结果输出NaN非数字!看看官方文档吧!二元运算符加法运算我们直接看个案例,二元运算符a + b这种//二元运算符 例如a+b console.log( 1 + '1');//11 //Number不传值都是0 console.log(1+null);//1 console.log([] + []);//空 //js中的隐式类型转换 console.log([] + {});//[object Object] console.log({} + {});//[object Object][object Object] 1+'1'因为运算当中存在字符串,所有二元运算符+会把数字1转换为字符串再与字符串'1'相加,所以输出的结果是'11'而对于传的值是1 + null,二元运算因为参数有数字,所以会把其他参数转换为数字,而null转换为数字就是0,所以结果是1而对于[] + []和[] + {}和{} + {},加法运算符会尝试将他们转换为字符串再进行拼接,所以我们看到的结果分别是空 和[object Object]和[object Object][object Object]我们看看官方文档如何描述的==运算对于==运算符,我们来看看一个案例分析和过程大家就懂啦!true == 1 // true 1 == {} => 1 == '[object Object]' => 1==NaN // false {} == {} // false console.log(false == []);// 0 == 0 对于true == 1和1 == {}和false == []二元==运算,它将会尝试去转换操作数到相同的类型,然后进行比较。运算true == 1和1 == {}会将它们转换为数字进行比较!而false == []怎么会尝试将[]转换为布尔值进行运算!最后答案揭晓![]==![] //输出?true or false? 我们拿到我们的题目!首先,我们观察到这个语句中有两个运算符==和!,我们要思考它们到底是怎样运算,我们就得知道它们的优先级!(运算符的优先级大家可以去查看一下),先告诉大家吧,!的运算优先级更高,所以我们先得知道![]是什么含义取非,把它们绑在一起,我们知道==会尝试将操作数转换为相同的类型,我们也这样做!这里也涉及到了一个抽象相等比较法!(es5.github.io/#x11.9.3)![]转换为布尔是!true也就是false于是我们的运算为[] == false,根据官方文档的抽象比较法的运算,==会尝试将两边转换为数字所以最终的效果为:0 == 0结果为true

1.面向对象及其三大特性
面向对象:对象是指具体的某一个事物,这些事物的抽象就是类,类中包含数据(成员变量)和动作(成员方法)。面向对象的三大特性:封装:将具体的实现过程和数据封装成一个函数,只能通过接口进行访问,降低耦合性。继承:子类继承父类的特征和行为,子类有父类的非 private 方法或成员变量,子类可以对父类的方法进行重写,增强了类之间的耦合性,但是当父类中的成员变量、成员函数或者类本身被 final 关键字修饰时,修饰的类不能继承,修饰的成员不能重写或修改。多态:多态就是不同继承类的对象,对同一消息做出不同的响应,基类的指针指向或绑定到派生类的对象,使得基类指针呈现不同的表现方式。在 C++ 中多态一般是使用虚函数来实现的,使用基类指针调用函数方法时,如果该指针指向的是一个基类的对象,则调用的是基类的虚函数;如果该指针指向的是一个派生类的对象,则调用的是派生类的虚函数。
热议话题

程序员职业生活大赏
2098参与37动态

2024年度总结
1726参与37动态

玩转AI人工智能
1740参与21动态

2024求职记录
1989参与14动态

又又又被我学到了
2068参与13动态

晒晒你的奖状和证书
427参与12动态

2025 新征程 在路上
309参与7动态

在摸一种很新的🐟
469参与7动态

今天是周五吗
379参与6动态

开工大吉
326参与5动态
热门项目
查看更多Flutter 美颜相机
开关美颜 拍照 拍视频 切换镜头 设置保存路径 添加滤镜
61星259浏览
OpenManus
No fortress, purely open ground. OpenManus is Coming.
44587星159浏览
doocs/leetcode
全面的 Leetcode 算法解题指南
33760星138浏览
mall
mall项目是一套电商系统,包括前台商城系统及后台管理系统,基于SpringBoot+MyBatis实现,采用Docker容器化部署。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。
80047星125浏览
photoprism
带 AI 功能的开源私人云相册
37064星105浏览