freeCodeCamp 每日编程挑战解析:用 Python 实现 Longest Word(最长单词查找)
2026/9/10 10:19:45 网站建设 项目流程

freeCodeCamp 每日编程挑战解析:用 Python 实现 Longest Word(最长单词查找)

【免费下载链接】freeCodeCampfreeCodeCamp.org's open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp

freeCodeCamp 开源仓库中内置了一套「每日编程挑战(Daily Coding Challenge)」体系,其中 Challenge 50: Longest Word 是一道典型的 Python 字符串处理题目:给定一个句子,返回其中长度最长的单词,忽略句点(.)对标点,遇到长度并列时返回最先出现的那个。本文将以此题为骨架,完整还原题目要求、三个判定用例、种子代码与官方参考答案,并深入到 freeCodeCamp 仓库的源码与配置中,说明这类题目在课程体系中的定位、测试执行机制与前端运行环境,帮助你既会做题、也看懂背后的工程实现。

题目全景:Challenge 50: Longest Word

本题对应的挑战文件位于 curriculum/challenges/english/blocks/daily-coding-challenges-python/68b7cadffed0e75a517da66f.md,文件头部的元数据清晰地定义了这道题的身份:

字段说明
id68b7cadffed0e75a517da66f挑战的唯一 ID,全仓库全局唯一
titleChallenge 50: Longest Word题目名称
challengeType29挑战类型编号,对应 Python 每日挑战
dashedNamechallenge-50URL 友好的短名称

其中challengeType: 29是关键:在 packages/shared/src/config/challenge-types.ts 中,29被定义为dailyChallengePy,即「Python 每日编程挑战」专属类型。同一个 block(daily-coding-challenges-python)中 200+ 道题目共用此类型,配套的 JavaScript 版本则使用28dailyChallengeJs)。从源码结构可以推断,这类题目在课程树中统一归入「Daily Coding Challenge」入口,由 curriculum/structure/blocks/daily-coding-challenges-python.json 按challengeOrder数组维护顺序——本挑战(id68b7cadffed0e75a517da66f)正是列表中的第 50 项。

需求拆解:两个边界规则

题目本身只给出两句话的要求,但浓缩了两个极易被忽略的边界规则:

  1. Given a sentence, return the longest word in the sentence.—— 核心目标:返回句子中最长的单词。
  2. Ignore periods (.) when determining word length.—— 计算长度时忽略句点.,也就是说句子末尾的句号不能算作单词长度的一部分。
  3. If multiple words are ties for the longest, return the first one that occurs.—— 出现多个同样长度的单词时,必须返回最先出现的那一个(即“第一胜出”规则,而不是“最后一个胜出”)。

第二、三条规则直接决定了参考答案的写法:官方解法中先用sentence.split()按空白拆词(这样"educational."会被拆成带句点的'educational.'),再用str.replace('.', '')去掉句点后比较长度,并且比较时使用严格大于(>)而非大于等于(>=),从而保证并列时保留更早出现的单词。

判定用例(Hints)逐条解读

仓库文件中的--hints--部分是题目的自动化测试,全部通过runPython在浏览器端执行 Python 代码,并用 Python 标准库unittestTestCase().assertEqual做断言。三个用例完整如下:

用例一get_longest_word("coding is fun")应返回"coding"

({test: () => { runPython(` from unittest import TestCase TestCase().assertEqual(get_longest_word("coding is fun"), "coding")`) }})

三个单词coding(6 字符)、is(2 字符)、fun(3 字符)中,coding最长且唯一,返回"coding"

用例二get_longest_word("Coding challenges are fun and educational.")应返回"educational"

({test: () => { runPython(` from unittest import TestCase TestCase().assertEqual(get_longest_word("Coding challenges are fun and educational."), "educational")`) }})

这是最核心的用例:educational.字面上是 12 个字符,但按规则忽略句点后是 11 个字符;若实现时直接用原始字符串比长度,会把句点计入,导致返回"educational."(或比较出错)。官方实现先replace('.', '')再比长度,恰好保证返回去掉句点的"educational"

用例三get_longest_word("This sentence has multiple long words.")应返回"sentence"

({test: () => { runPython(` from unittest import TestCase TestCase().assertEqual(get_longest_word("This sentence has multiple long words."), "sentence")`) }})

此用例同时考验“忽略句点”和“并列取先”两条规则:multiplesentence均为 8 字符,但sentence在句子中出现得更早,因此正确答案是"sentence"而不是"multiple"。若比较逻辑使用>=,则会错误地返回"multiple"

种子代码与解题起点

题目在--seed-contents--中给定了函数骨架,学习者只需填充函数体:

def get_longest_word(sentence): return sentence

可以看出,默认实现是直接原样返回输入的sentence字符串,这显然无法通过任何用例——这正是挑战的起点:学习者需要自己完成「拆词 → 清洗标点 → 遍历比较 → 返回最长项」的完整逻辑。

官方参考答案逐行剖析

--solutions--段提供了官方的完整解法:

def get_longest_word(sentence): words = sentence.split() longest = '' for word in words: clean_word = word.replace('.', '') if len(clean_word) > len(longest): longest = clean_word return longest

逐行拆解其设计意图:

代码作用与要点
1words = sentence.split()默认按任意空白字符(空格、制表符、换行)切分句子为单词列表,句点仍附着在最后一个单词上
2longest = ''初始化最长单词为空字符串,保证任何非空单词的长度(≥1)都能覆盖它
3for word in words:顺序遍历,天然保证“先出现的单词优先被记录”
4clean_word = word.replace('.', '')去除该单词中的所有句点,实现“忽略句点”规则
5if len(clean_word) > len(longest):严格大于才更新,并列时保留先出现的单词
6longest = clean_word注意这里保存的是清洗后的单词(不带句点),与用例二期望的输出一致
7return longest返回结果

该解法的时间复杂度为 O(n·m)(n 为单词数,m 为平均单词长度),空间复杂度 O(n)。replace('.', '')会移除单词内所有句点(如缩写e.g.会被处理成eg),在本题目给定用例下不影响正确性。

变体与扩展:从官方解到通用解

官方解法足够简洁,但了解变体有助于真正吃透“并列取先”与“忽略标点”这两个核心规则:

写法一:key参数 +max(等长取先需谨慎)

def get_longest_word(sentence): words = [w.replace('.', '') for w in sentence.split()] return max(words, key=len)

max在遇到相同key值时返回列表中最先出现的项,因此天然满足“并列取先”规则。但注意:若用key=lambda w: len(w)且在比较前未清洗句点,则会在用例二上失败。

写法二:显式清洗 + 线性扫描(语义最清晰)

def get_longest_word(sentence): longest = '' for word in sentence.split(): clean = word.strip('.,!?;:') if len(clean) > len(longest): longest = clean return longest

strip('.,!?;:')视作对“忽略句点”规则的泛化——题目只要求忽略.,但把规则扩展到常见句末标点也不需要改动循环结构,这体现了该题解法的可扩展性。

关于并列取先的深层原因if len(clean_word) > len(longest)使用严格大于,意味着后一个同长度单词永远不会覆盖前一个,这正是「return the first one that occurs」的代码级体现。这也是本类题最常被忽视的考点。

工程视角:这类题目在 freeCodeCamp 中如何运转

理解了题目本身后,值得回到仓库源码,看看challengeType: 29是如何被整个体系识别和驱动的。

挑战类型的注册与分发

packages/shared/src/config/challenge-types.ts 是类型的“总注册表”:

  • const dailyChallengePy = 29;(第 31 行)定义了类型编号;
  • viewTypes中将dailyChallengePy映射为'classic'视图(第 135 行),意味着本题与经典代码挑战共用同一套编辑器界面;
  • submitTypes中映射为'tests'(第 176 行),即提交后通过运行测试来判定对错;
  • getIsDailyCodingChallenge(challengeType)(第 187 行)通过dailyCodingChallengeTypes列表识别「每日挑战」类型(含 JS 与 Python 两种);
  • getDailyCodingChallengeLanguage(challengeType)(第 204 行)将dailyChallengePy映射为'python',用于确定运行语言。

因此,当学习者在前端进入本题时,系统会走「classic 编辑器 + Python 语言 + tests 提交」这一组合管线。

测试如何被执行为 Python 断言

本题的--hints--使用了runPython(...)包裹的unittest断言,这在 e2e 测试中有直接对应物:e2e/daily-coding-challenge.spec.ts 中构造的 mock 挑战数据python.tests[0].testString就是一个({test: () => { runPython(\assert True == True`)}})形式(第 41 行),说明该格式是每日挑战 Python 测试的标准载体。从仓库结构可以推断,这类测试在前端通过内置的 Python 运行时(浏览器内执行)运行,与项目自带的 Python 课程(如pythonProjectpyLab` 等类型)共用同一套执行机制。

每日挑战的入口与路由

在课程地图与落地页上,每日挑战有独立的入口组件:client/src/components/daily-coding-challenge/widget.tsx 渲染「今天的挑战」与「历史归档」两个按钮,分别指向/learn/daily-coding-challenge/${getMonthDayUsCentral()}/learn/daily-coding-challenge/archive。日期计算依赖 client/src/components/daily-coding-challenge/helpers.ts 中的getTodayUsCentral()——基于美国中部时区(America/Chicago)计算“今天”,并对 2 月 29 日做特殊映射(toMonthDay02-29转为02-28),确保每年的同一天都对应同一道题。Challenge 50 这类题即通过「按月-日归档」的方式被收录进每日挑战体系。

在课程树中的定位

curriculum/structure/blocks/daily-coding-challenges-python.json 以challengeOrder数组维护了全部 Python 每日挑战的顺序,本挑战(id68b7cadffed0e75a517da66f)排在第 50 位,前后分别是Challenge 49: CSV Header ParserChallenge 51: Phone Number Formatter。文件头还声明了"usesMultifileEditor": true"helpCategory": "Python",前者表示题目支持多文件编辑器(本题虽只有一个函数,但环境按多文件模式加载),后者将题目归入 Python 帮助分类,用于学习社区的问题归类。

本地体验:如何运行与验证

仓库采用 pnpm workspace 管理(见根目录 package.json 与 pnpm-workspace.yaml)。如果你想在本地复现本题或亲手验证解法,可以按以下方式操作:

  1. 仅验证算法逻辑:直接把官方解法粘贴到任意 Python 3 环境运行,三个用例等价于:
from unittest import TestCase def get_longest_word(sentence): words = sentence.split() longest = '' for word in words: clean_word = word.replace('.', '') if len(clean_word) > len(longest): longest = clean_word return longest tc = TestCase() tc.assertEqual(get_longest_word("coding is fun"), "coding") tc.assertEqual(get_longest_word("Coding challenges are fun and educational."), "educational") tc.assertEqual(get_longest_word("This sentence has multiple long words."), "sentence") print("All assertions passed")
  1. 完整运行 freeCodeCamp 前端与课程环境:参考仓库根目录 README.md 的指引安装依赖并启动开发服务器,即可在浏览器中进入每日挑战页面实际编写并提交代码;题目的判定逻辑由上文提到的 hints 测试驱动,无需额外配置。

小结

Challenge 50: Longest Word 虽是一道入门级字符串题,但它完整覆盖了「切分 → 清洗 → 比较 → 保持首次出现」四个典型处理步骤,其官方实现更是将「忽略句点」与「并列取先」两条边界规则体现得淋漓尽致。通过阅读其挑战元数据(challengeType: 29)、共享配置(challenge-types.ts)、组件入口(widget.tsx)与 e2e 用例(daily-coding-challenge.spec.ts),你不仅能独立 AC 这道题,还能理解 freeCodeCamp 每日编程挑战从题目文件到前端运行、再到自动化断言的完整工程链路,为继续研究该开源项目的课程体系打下基础。

【免费下载链接】freeCodeCampfreeCodeCamp.org's open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询