ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

零基础学Python必刷的两道题:李白打酒与词频统计

零基础学Python必刷的两道题:李白打酒与词频统计 前两天在社群里看到有人问零基础学Python到底刷什么题我打开LeetCode第一题就看不下去。这个问题我太有发言权了。我带过的零基础同学几乎全是从同一组题入门的李白打酒和英文词频统计。这两道python编程题一道练逻辑、一道练数据难度刚好卡在能看懂、要动脑、写完有成就感的位置。文章不长但我把从读题、推导、写代码到踩坑的完整过程都放进来了无论你是刚开始学还是已经会写点基础语法想找手感都可以按这个流程走一遍。1. 为什么是这两道题一本逻辑题一本数据题1.1 李白打酒一道连初中生都能看懂、程序员却能翻车的题李白打酒是网上流传很广的一道古典算术题原文大概是李白无事街上走提壶去买酒。遇店加一倍见花喝一斗。三遇店和花喝光壶中酒。试问壶中原有多少酒这道题最妙的地方在于题目本身只有几句话不需要任何专业背景小学文化就能读懂。但真正动手写出代码后你会发现自己面临的不只是算出来这么简单而是怎么把文字逻辑转成循环和分支结构怎么处理最后一次状态已知初始状态未知的逆向推导。很多人第一反应是设未知数列方程但题目换一个表述比如遇店加两倍四遇店和花方程就乱套了。用代码来解决其实更通用、更稳。1.2 词频统计把工程能力藏进一道小学题里英文词频统计这题看起来也简单给你一段英文文本统计每个单词出现多少次输出出现次数最多的前几个单词。但真正动手做的时候你会同时遇到字符串清洗、大小写归一化、字典计数、排序、切片五个基础任务。这些任务单独拿出来都不难合在一起偏偏能卡住一大片初学者——不是语法不会而是先干什么后干什么的流程感没有建立。这两道题放在一起恰好覆盖了Python学习路径上最重要的两条分支一条是逻辑推导一条是数据处理。前者训练你的脑子后者训练你的工程手感。很多教程让你刷五十道题其实不如把这两道题老老实实推一遍、写一遍、改三遍。1.3 两道题覆盖的语法点对照我做了个简单的对照表方便你自查知识点李白打酒词频统计掌握等级循环结构倒推循环、穷举验证遍历单词列表必会分支结构判断遇店还是遇花判断字符类型必会函数定义函数封装与参数传递构造可复用统计函数必会列表操作正向序列模拟split切分、列表推导必会数组切片不涉及items[:top_n] 取前三必会字典操作不涉及单词计数、get默认值必会类型转换整数和浮点数、分数lower、字符串与列表常踩坑正则/高级模块不涉及re模块、Counter进阶如果你能不看答案把这两道题都写出来说明你至少可以独立开始做小型数据处理和简单的算法练习题了。2. 第一道李白打酒逆向递推的完整推导2.1 题意里的那个坑三遇店和花到底怎么理解我第一次做这道题就栽在了题意理解上。题目说三遇店和花很多人的第一反应是先遇三次店再遇三次花。但细想一下如果连着遇到三次店酒量翻倍翻倍再翻倍再连续喝三次也能喝光但这不符合见花喝一斗的随机性。更符合文字原意的理解是李白每走一段路遇到一次店酒量加倍走一段路遇到一次花喝掉一斗这个过程重复三次也就是操作顺序是店、花、店、花、店、花。这个理解非常关键因为它直接决定你要写的循环结构是什么样。市面上有些版本的题把三遇店和花解释成一次店一次花算一遇三次就是三种店花交替那代码里的循环就要写成偶数次操作。还有版本说遇店加一倍遇花喝一斗不分顺序随机遇到六次。所以拿到编程题的第一步永远是把题意中的隐含规则敲死不然代码写得再漂亮都是错的。2.2 从喝光壶中酒往回收逆向推演已知最后酒量为0斗最后一步是见花喝一斗。那我就把整个流程倒过来看原来遇店加一倍的逆操作是除以2原来见花喝一斗的逆操作是加上1斗。从最终状态0倒着往前推遇到花的逆操作就加一遇到店的逆操作就除以2。具体推一下初始未知假设为x。第一次遇店酒变2x。第一次见花酒变2x-1。第二次遇店酒变4x-2。第二次见花酒变4x-3。第三次遇店酒变8x-6。第三次见花酒变8x-7 0。解得x 7/8斗。也就是说李白出门时壶里只有不到一斗酒。用逆向推更直接最后一次喝光前酒量必须是1斗再往前推一步遇店加一倍之前酒量必须是0.5斗继续往回收最后能得到7/8斗。这个过程用循环来表达比解方程更贴合编程思维。2.3 一口气写出能跑的版本我习惯先把操作序列显式写出来再去写循环。代码如下sequence [店, 花, 店, 花, 店, 花] wine 0.0 for step in reversed(sequence): if step 店: wine wine / 2 # 遇店加一倍倒推时除以2 else: wine wine 1 # 见花喝一斗倒推时加回一斗 print(wine) # 输出 0.875这里用reversed(sequence)把正向流程倒过来遍历。我每次给同学讲这段代码都要强调一点倒推时先判断是店还是花再决定做除法还是加法别把顺序写反了。有人直接把操作序列写成[花, 店, 花, 店, 花, 店]再正向遍历效果是一样的但思路一定要说清楚。2.4 把解法打包成函数再写一个正向验证光算出7/8斗还不够稳我还会加一个正向验证函数从7/8开始模拟六步确认最后结果为0。这也是老手写代码的习惯逆向算完正向验证双重保险。def li_bai_wine(rounds3): 通过逆向递推计算初始酒量 wine 0.0 for _ in range(rounds): wine 1 # 逆花 wine / 2 # 逆店 return wine def verify_wine(initial, rounds3): 正向模拟店花交替验证酒是否喝光 wine initial for i in range(rounds * 2): if i % 2 0: wine * 2 # 遇店 else: wine - 1 # 见花 return wine init li_bai_wine(3) print(init) # 0.875 print(verify_wine(init)) # 0.0这里当i % 2 0时执行遇店操作刚好模拟店花店花店花的顺序。如果把rounds3换成4就变成四遇店和花代码不需要大改这就是封装成参数的价值。你在写这类题的时候也可以试着把可变的部分全部抽成参数以后题目稍微一变你改一行就行。3. 第二道英文文本词频统计Top N3.1 需求看起来简单拆开其实有四步词频统计的核心需求一句话就能说清楚输入一段英文文本输出出现次数最多的前N个单词。但这句话背后藏着四个独立步骤清洗文本把标点符号、换行符去掉统一大小写让Python和python算同一个词切分单词并计数用字典记录每个词出现次数排序并切片按频次降序拿到Top N。我见过太多人直接拿一个变量count从头数到尾数到一半发现单词太多了根本没法数。这是因为没有先把整体流程拆成输入、处理、输出三个环节。写代码之前先问自己三个问题输入长什么样中间要做哪些变换最终要得到什么格式的结果这一步想清楚后面基本不会跑偏。3.2 第一版实现换标点、切词、字典计数、排序切片我给出一个很朴素的版本完全不用任何高阶模块适合零基础理解def top_words(text, top_n3): # 第一步把标点和换行统一替换成空格 for ch in ,.!?;:()\-\n: text text.replace(ch, ) # 第二步统一小写并切分成单词列表 words text.lower().split() # 第三步字典计数 freq {} for word in words: freq[word] freq.get(word, 0) 1 # 第四步排序频次高的在前频次相同按字母顺序 items sorted(freq.items(), keylambda x: (-x[1], x[0])) # 第五步切片取前 top_n 个 return items[:top_n]测试用的文本我故意写了一个需要清洗的版本sample_text Python is powerful, Python is easy, and Python is fun. Python is also popular. Python makes data science easier! for word, count in top_words(sample_text, 3): print(f{word}: {count})运行结果python: 5 is: 3 and: 1注意第三名的处理。频次为1的词有一堆我给排序规则加上了(频次降序, 字母升序)所以and排在了easy、fun、popular前面。如果你不在意并列词的顺序也可以只用keylambda x: x[1]加一个reverseTrue但推荐做成稳定的排序方便复现结果。3.3 借这道题把切片和排序的细节彻底搞明白items[:top_n]这行代码就是数组切片最典型的用法取列表前N个元素。切片的完整语法是列表[start:stop:step]省略start表示从开头取省略stop表示取到结尾[:3]就是取索引0、1、2三个元素。很多教程把切片讲得很玄其实你把它理解成复制一份列表的指定区间就行。排序这里其实还有个小坑freq.items()返回的是字典的键值对视图直接对它调用sorted()会得到一个按字典序排序的键值对列表而不是按频次排序。所以必须用key参数告诉Python按哪个字段排。lambda x: (-x[1], x[0])的意思是对每个元素x一个元组x[1]是频次x[0]是单词频次取负就能实现降序频次相同时再按单词升序。这个技巧在数据分析、日志统计里特别常用值得记下来。3.4 换用Counter和正则代码少一半当你理解了上面的基础版就可以换个更高效的写法了。Python的collections.Counter专门用来统计可哈希对象的频次re.findall可以一次性提取所有符合条件的单词from collections import Counter import re def top_words_fast(text, top_n3): words re.findall(r[A-Za-z], text.lower()) return Counter(words).most_common(top_n)这段代码只有三行但做的事和前面的函数一模一样。re.findall用正则表达式把字母和撇号全部挑出来跳过数字和其他符号text.lower()统一转为小写Counter(words).most_common(top_n)一行完成计数和取TopN。如果你在别人的项目里看到这种写法别懵它背后就是刚才那五步的压缩版。3.5 我在这道题上见过最多的三类错误第一类不做大小写归一化。Python.lower()变成python否则同一个词会被统计成两个词。第二类没清洗标点fun.和fun会被当成两个词导致明明只有一个单词却出现两次。这类错误特别隐蔽因为你看输出时容易忽略句号的存在。第三类手动计数时忘了处理字典中不存在的键直接写freq[word] 1程序一运行就报KeyError。正确姿势是freq.get(word, 0) 1get方法在键不存在时返回默认值0。我建议新手第一版用最原始的方式逐步写出看看每一步的输出是什么比如先只执行text.lower().split()并打印再执行计数并打印最后再排序。逐步调试一遍比你闷头写十遍都有用。4. 两道题背后的通用解题框架和排坑清单4.1 五步解题流程两道题都在验证同一套打法这两道题看完了你会发现它们的解题流程惊人地一致。我已经把这套流程固定成了做编程题的五步分享给你列出已知条件。李白打酒已知终态为0、店花交替三次词频统计已知输入是文本、输出是前N个单词。把条件列出来等于把题目翻译成了程序语言的变量。确定核心数据结构。李白打酒需要一个浮点数变量存酒量词频统计需要一个字典存频次。数据结构选对了写出来就是顺畅的选错了就会越写越乱。先写最朴素的版本。不要一开始就想我要写得多优雅先用if加for把逻辑跑通结果对了再谈优化。考虑边界情况。词频统计里空文本怎么办只有标点没有单词怎么办李白打酒里rounds传0会怎样。加了边界处理你的代码才敢放到真实场景。反向验证结果。李白打酒逆向算完接着正向跑一遍词频统计打印一下原始words列表看一眼。验证不是浪费时间是让你睡个好觉。4.2 高频BUG复盘表我在答疑过程中收集了一些初学者特别容易踩的坑做成表格供你对照错误类型典型表现原因解决方式索引越界IndexError: list index out of range循环里取words[i1]没判断长度用for word in words遍历少用手动索引变量覆盖统计结果永远是0或1在循环体里重置了计数变量打印循环内变量值检查语句缩进层级类型拼接TypeError: can only concatenate str试图用拼接字符串和数字先str(count)再拼接或用f-string原地删除遍历列表时删元素导致跳过for w in words里直接remove(w)改用新列表保存结果或倒序遍历字典键不存在KeyError对不存在的键做运算用get(key, default)大小写不统一Python和python两个词没有调用lower()切词前统一小写这里特别提一下for w in words里直接remove的问题很多人觉得这是小问题实际上它会让你漏掉元素因为删除后列表索引前移循环却还在按原索引向后走后面一半元素会被跳过。我的建议是要过滤就构造新列表别在原列表上边遍历边删。4.3 写代码时让自己像老手的几个小习惯老手和新手写的代码在能跑的前提下最大的区别是三个一个有名字一个全是a、b、c一个能分步骤验证一个只能一把梭子从头跑到尾一个主流程清晰一个所有逻辑全堆在全局代码里。所以我的建议是变量名用word、freq、wine不用x、y、z每个关键步骤后加一个print看一眼中间值确认没问题再继续把核心流程封装成函数主程序只留输入输出。这些习惯现在看起来有点浪费时间但等你开始写超过一百行的脚本、需要协同调试的时候你就知道它们有多值钱了。5. 做完这两道题接下来往哪走5.1 李白打酒的变式训练想要把这道题的价值榨干最简单的变式就是改参数。把三遇店和花改成四遇店和花测试你封装的函数能否直接复用把见花喝一斗改成见花喝两斗检查逆向递推的加一操作是否改对把遇店加一倍改成遇店加百分之五十验证倒推时除以1.5而不是除以2。这些变式不需要新知识但能帮助你彻底掌握逆向递推的边界条件。如果还想进阶可以试试把正向模拟做成一个可交互程序输入初始酒量程序模拟每一次遇店遇花的过程实时打印壶中剩余酒量。这样一个命令行小工具就出来了比单纯做数学题有趣得多。5.2 词频统计的升级方向词频统计的升级方向就很鲜明了。第一步是换成中文文本Python处理中文需要先分词常用的是jieba库切出来的词存进列表之后后面的字典计数、排序、切片逻辑完全不变。第二步是结合数据可视化把Top10单词画成柱状图用matplotlib几十行就能出来一张漂亮的图。第三步是做一个文件版词频统计支持传入一个txt文件路径自动读取全文并返回统计结果这样它就不再是练习题而是一个可以用起来的工具。我个人的经验是一道题刷完至少做一次变式、一次场景升级才算真的吃透了。很多人刷题只求做出来做完就扔过两周再看代码跟看天书一样。你试着把这两道题改一改、扩展一下你对代码还能这么写的体会会比刷十道新题还深。这两道题最大的价值不在于它们本身有多难而在于它们逼你走完一遍读题、拆解、实现、验证、优化的完整流程。我带过的同学里凡是认认真真把这两道题写成函数、加好验证、跑通变式的后面学Python都明显顺利很多。编程这东西最终拼的不是记了多少API而是你把多少基础过程变成了手感和直觉。这两道题就是练手感和直觉的起点。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表