news 2026/9/11 7:59:41

freeCodeCamp 每日编码挑战解析:Challenge 330「lowercase words」过滤全小写单词的实现与验证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
freeCodeCamp 每日编码挑战解析:Challenge 330「lowercase words」过滤全小写单词的实现与验证

freeCodeCamp 每日编码挑战解析:Challenge 330「lowercase words」过滤全小写单词的实现与验证

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

本篇技术指南围绕 freeCodeCamp 开源课程仓库中的每日编码挑战Challenge 330: lowercase words(挑战源文件)展开,完整解读题目要求、测试用例与官方参考解法,并结合仓库中每日编码挑战的课程结构、前后端实现与数据灌入脚本,说明这类挑战在 freeCodeCamp 中的实际运行链路。读完本文,你将掌握基于split/filter/join流水线处理字符串过滤问题的方法,并理解每日编码挑战从 Markdown 挑战文件到数据库、再到前端渲染与断言验证的完整机制。

一、挑战背景:什么是 freeCodeCamp 每日编码挑战

在进入具体题目之前,先交代它所在的环境。freeCodeCamp 仓库中维护着一个独立的课程块(block),名为daily-coding-challenges-javascript,其元数据定义在 curriculum/structure/blocks/daily-coding-challenges-javascript.json。该块共包含365 道挑战(Challenge 1 至 Challenge 365),按天发布,isUpcomingChangetrue,采用legacy-challenge-list布局,并启用usesMultifileEditor(多文件编辑器)。Challenge 330 正是其中第 330 道,紧邻 Challenge 329 "Bucket Fill" 与 Challenge 331 "Nearest Multiple"。

从块结构可以推断,每日编码挑战以"一日一题"的形式存在:仓库根目录中的 tools/daily-challenges/seed-daily-challenges.ts 将挑战按每日递增的日期批量写入数据库,第一条挑战对应2025-08-11(UTC),此后每条挑战日期顺延一天;该脚本还以常量EXPECTED_CHALLENGE_COUNT = 365校验挑战数量,确保一年 365 天每天都有题可练。

二、题目要求逐字解读

Challenge 330 的英文原题如下(位于挑战文件的# --description--部分):

Given a string, return only the words that are entirely lowercase, in their original order and with a space between each word.

将其拆解为三条核心约束:

  1. 过滤条件:只保留"完全小写"的单词(entirely lowercase),即单词中不允许出现任何大写字母;
  2. 顺序保持:保留单词在原始字符串中的相对顺序(original order);
  3. 输出格式:每个保留的单词之间以一个空格分隔(with a space between each word)。

题目没有要求修改单词本身、没有要求去重、也没有要求排序,因此这是一个典型的"筛选(filter)"型字符串问题。值得注意的是,toLowerCase()只作用于 ASCII 字母大小写转换,因此像数字、标点这类"没有大小写之分"的字符不受影响——例如单词"123"与自身的小写形式相等,会被视为"全小写单词"而保留(这与参考解法中word === word.toLowerCase()的判断逻辑一致)。

三、测试用例(hints)与断言行为分析

挑战文件通过 5 组测试用例(# --hints--部分)定义了通过标准。freeCodeCamp 的挑战机制中,每个 hint 都对应一条由assert构成的测试字符串,只有全部通过才算挑战完成。下表完整列出这 5 组用例及其行为特征:

输入字符串期望输出覆盖的边界特征
"hello GOOD world""hello world"过滤中间的全大写单词GOOD
"these are all lowercase""these are all lowercase"全小写输入应原样返回(不变式)
"less is NoT more""less is more"过滤混合大小写单词NoT(首字母大写 + 全大写组合)
"DonT eat pizza every OTHER day""eat pizza every day"同时过滤DonTOTHER,保留单词间仍以单个空格连接
"the Super quick AND snEaky brown fox Leapt anD jumped over aNd AROUND the lazy SloW dog""the quick brown fox jumped over the lazy dog"长句中过滤多种大写形态(首字母大写、全大写、大小写混杂)

这些用例的assert.equal断言写法如下(节选自 挑战源文件):

assert.equal(getLowercaseWords("hello GOOD world"), "hello world"); assert.equal(getLowercaseWords("DonT eat pizza every OTHER day"), "eat pizza every day");

从测试设计上可以归纳出判定"全小写单词"的标准:只要单词中存在任何大写字母(无论出现在首字母还是单词中部),该单词就被排除NoTDonTsnEaky这类"单词内含大写"的用例专门用来防止解题者只按word[0]判断首字母大小写。

四、种子代码(seed)与官方参考解法

4.1 种子代码

挑战文件# --seed--部分提供了函数骨架,学习者需要在此完成实现:

function getLowercaseWords(str) { return str; }

种子代码直接返回原字符串,意味着不实现任何逻辑时全部测试都会失败。每日编码挑战在课程中被归类为challengeType: 28(对应@freecodecamp/shared中定义的 JavaScript 每日挑战类型),在客户端渲染时通过多文件编辑器编辑,函数在浏览器环境中运行并被测试断言验证。

4.2 官方参考解法

挑战文件# --solutions--部分给出的官方解法仅用一行完成全部需求:

function getLowercaseWords(str) { return str.split(" ").filter(word => word === word.toLowerCase()).join(" "); }

其执行流程可以拆解为三个步骤:

  1. str.split(" "):以单个空格为分隔符,将字符串拆分为单词数组。注意这里使用的是普通空格" ",因此多个连续空格会产生空字符串元素(空字符串"" === "".toLowerCase()成立,会被filter保留,这是该解法对"多余空格"的一种隐性处理方式,但从测试用例看题目输入均为单词间单个空格);
  2. .filter(word => word === word.toLowerCase()):保留"与其全小写形式完全相等"的单词。这是整个挑战的核心判定:只要单词含有一个大写字母,word.toLowerCase()就会改变其内容,比较结果为false,单词被过滤;
  3. .join(" "):以单个空格连接过滤后的单词,保证输出中单词间只有一个空格,与题目"with a space between each word"的要求完全吻合。

这一解法充分利用了 JavaScript 字符串与数组的链式 API,是函数式风格(map/filter/reduce 思维)在字符串处理中的典型示范。

五、深入学习路径:可替换实现与易错点

为了让解题者不只停留在"背答案"层面,这里补充几种等价实现思路及其对应的易错点,可用于自行验证或拓展练习:

正则表达式方案:使用/\b[a-z]+\b/之类的模式匹配全部由小写字母组成的词,但需要注意\b词边界与标点、数字的交互,且正则方案无法直接复用上面 5 组用例的语义(例如单词含数字时行为会与参考解法不同),因此以通过挑战测试为准,推荐使用与官方解法语义完全一致的word === word.toLowerCase()判定

显式循环方案

function getLowercaseWords(str) { const words = str.split(" "); const result = []; for (const word of words) { if (word === word.toLowerCase()) { result.push(word); } } return result.join(" "); }

容易踩坑的点

  • 只判断首字母大小写(如word[0] === word[0].toLowerCase()),会错误地保留NoTDonT这类含内部大写字母的单词;
  • 使用word === word.toUpperCase()反推"全大写"再取反,会误伤"123""!!"等不含字母的 token(它们toUpperCase()后仍等于自身);
  • 忘记join(" "),返回数组而不是字符串,导致assert.equal全部失败;
  • 使用replacematch时未考虑大小写混合单词被整体剔除的语义。

六、从挑战文件到线上运行的完整链路

Challenge 330 并非孤立的练习题文件,它处于 freeCodeCamp 每日编码挑战的完整生产链路中。结合仓库源码,这条链路可以分为四段:

6.1 课程侧:挑战文件与结构定义

挑战的原始内容(题目描述、hints、seed、solution)存放在 curriculum/challenges/english/blocks/daily-coding-challenges-javascript/ 下,每个挑战一个 Markdown 文件。其 frontmatter 中challengeType: 28是关键标记:它让 curriculum/schema/challenge-schema.js 这类 Joi 校验(challengeType: Joi.number().min(0).max(33).required())以及课程构建流程能识别出这是一道每日编码挑战,并按对应模板渲染。

6.2 数据侧:GraphQL 抽取与数据库灌入

tools/daily-challenges/seed-daily-challenges.ts 配合 tools/daily-challenges/helpers.ts 完成挑战数据入库:

  • 脚本通过 GraphQL 查询(端点http://localhost:8000/___graphql,需要客户端以"显示即将上线内容"模式运行)从dev-playground超级块中按challengeOrder升序拉取全部 JavaScript 与 Python 挑战;
  • combineChallenges将同一编号的 JS/Python 挑战合并为一条文档,写入 MongoDB 的DailyCodingChallenges集合,并使用挑战的id作为文档_id(代码注释强调"DO NOT CHANGE THE ID",因为它同时被completedDailyCodingChallenges[]引用);
  • 脚本内置多重一致性校验:JS 与 Python 挑战数量必须相等、标题必须一致、描述必须一致、测试数量必须一致,数量必须等于 365,任何一项不满足都会抛出错误中断灌入;
  • 每条挑战按START_DATE(2025-08-11 UTC)加上i * ONE_DAY_IN_MS生成日期,脚本对开始日期做了硬编码保护,防止发布后意外改动。

灌入后的文档结构为:_idchallengeNumbertitle(已去掉Challenge N:前缀)、datedescription(已去除解析器添加的<section id="description">包装,见removeSection)、javascript.tests/challengeFilespython.tests/challengeFiles

6.3 API 侧:按日期提供挑战内容

api/src/daily-coding-challenge/routes/daily-coding-challenge.ts 注册了 6 个公开的 GET 路由,供客户端按日期或天数获取挑战:

  • GET /daily-coding-challenge/date/:date:按YYYY-MM-DD精确日期查询,日期晚于今天(美国中部时间)会返回 404;
  • GET /daily-coding-challenge/day/:day:按MM-DD查询,内部通过getSourceDate换算到源日期(Challenge 330 对应的2026-06-19这类日期即由该逻辑得出);
  • GET /daily-coding-challenge/today:返回"今天"的挑战;
  • GET /daily-coding-challenge/month/:month:按YYYY-MM返回当月挑战列表(只含idchallengeNumberdatetitle),且只返回不晚于今天的记录;
  • GET /daily-coding-challenge/all:返回全部挑战的简要列表;
  • GET /daily-coding-challenge/newest:返回最新一条挑战的日期。

路由全部使用 Fastify + TypeBox schema 做参数校验(定义于 api/src/daily-coding-challenge/schemas/),非法日期格式返回 400,未找到返回 404,并对各类结果打点 Sentry 指标(如dcc.challenge_vieweddcc.challenge_not_found)。挑战的提交不经过这些路由,而是走主挑战完成流程(见 api/src/daily-coding-challenge/README.md)。

6.4 客户端侧:拉取、校验与渲染

client/src/client-only-routes/show-daily-coding-challenge.tsx 是前端渲染入口:

  1. 根据 URL 中的日期调用/daily-coding-challenge/day/{MM-DD}获取挑战数据;
  2. 使用 client/src/utils/daily-coding-challenge-validator.ts 对返回数据做 schema 校验,校验失败则抛出错误并显示"未找到"页面;
  3. formatChallengeData将数据库文档重新组装为ShowClassic组件所需的 props,其中 JavaScript 侧challengeType: 28helpCategory: 'JavaScript',文件内容来自javascript.challengeFiles[0].contents,测试来自javascript.tests——Challenge 330 的 5 组assert.equal正是以这种方式进入浏览器测试运行器的;
  4. 用户在编辑器内编辑getLowercaseWords,点击运行后由测试断言逐条验证,全部通过即完成挑战。

此外,client/src/components/daily-coding-challenge/widget.tsx 提供了首页/学习地图上的入口组件,将用户引导至/learn/daily-coding-challenge/{MM-DD}当天的题目页面。

七、在本地运行与调试 Challenge 330

若要在本地环境亲手验证 Challenge 330 的解法,可以按以下方式操作(仓库为只读,以下均只涉及查看与本地运行):

  1. 本地校验解法正确性:在 Node.js 环境直接粘贴参考解法与 5 组断言即可快速验证:
function getLowercaseWords(str) { return str.split(" ").filter(word => word === word.toLowerCase()).join(" "); } console.log(getLowercaseWords("hello GOOD world")); // "hello world" console.log(getLowercaseWords("DonT eat pizza every OTHER day")); // "eat pizza every day" console.log(getLowercaseWords( "the Super quick AND snEaky brown fox Leapt anD jumped over aNd AROUND the lazy SloW dog" )); // "the quick brown fox jumped over the lazy dog"
  1. 查看课程结构与相邻挑战:块定义 curriculum/structure/blocks/daily-coding-challenges-javascript.json 中challengeOrder完整列出了 365 道挑战的 id 与标题,Challenge 330 的 id 为6a1d9f98e819ed70a0e994dc,可通过它定位同目录下的相邻挑战(329、331)作对比练习。

  2. 本地完整链路运行(可选):按照 tools/daily-challenges/README.md 的指引,将sample.env复制为.env、安装依赖、以"显示即将上线内容"模式运行客户端,然后执行pnpm seed-daily-challenges将挑战灌入本地 MongoDB,即可配合 API 与前端页面复现完整的每日挑战体验。注意该流程面向本地开发环境,脚本本身也提醒生产库的开始日期不应改动。

八、小结

Challenge 330 "lowercase words" 是一道精炼的字符串过滤题:其核心判定word === word.toLowerCase()一行代码便概括了"完全小写"的定义,配合split/filter/join组成标准的函数式字符串处理流水线。同时,从这道题出发可以看到 freeCodeCamp 每日编码挑战的工程全貌——挑战以 Markdown 文件为唯一事实来源,经 GraphQL 抽取、一致性校验与日期灌入写入数据库,再通过 Fastify API 按日/月/全部等维度对外提供,最终由客户端校验 schema、组装 props 并交给浏览器测试运行器逐条断言。理解这条链路,既能帮助你快速通过本挑战,也能为你参与 freeCodeCamp 课程生态的阅读与维护提供清晰的上下文。

延伸阅读:挑战相邻题目(Challenge 329: Bucket Fill、Challenge 331: Nearest Multiple)可作同类型字符串/数学练习;每日挑战的 Python 版本位于同结构块的daily-coding-challenges-python中。

【免费下载链接】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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 7:57:01

IEEE会议投稿指南:CCF C类会议策略与技巧

1. 项目概述作为一名常年混迹学术圈的科研狗&#xff0c;今天想和大家聊聊IEEE会议投稿那些事儿。最近刚收到一封邮件提醒&#xff0c;某个CCF推荐C类会议的截稿日期快到了&#xff0c;录用率29.8%这个数字让我眼前一亮。这个录用率在学术会议中算是比较友好的&#xff0c;特别…

作者头像 李华
网站建设 2026/9/11 7:53:37

SpringBoot+Vue3居家办公系统实战:从数据库设计到前后端部署

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 7:51:21

编程Agent平台全盘点:17款工具分类详解与选型指南

2022年底我在老项目里第一次接触AI编程补全时&#xff0c;内心其实很平静&#xff1a;无非是多按几次Tab&#xff0c;少敲几个样板函数。可到了2024年下半年&#xff0c;事情开始变得不对劲——GitHub Copilot开始在多文件里连续修改&#xff0c;Cursor能用自然语言把整个支付模…

作者头像 李华
网站建设 2026/9/11 7:48:26

Android心率监测系统开发:BLE通信、PPG信号处理与实时波形实现

简介&#xff1a;一套面向Android毕业设计与课程设计的完整心率监测系统方案&#xff0c;覆盖安卓客户端、服务端与数据库三大模块。安卓端实现用户登录注册、通过手机摄像头实时测量心率、测试历史记录&#xff0c;并将结果上传至网站服务器&#xff1b;网站端支持管理员登录、…

作者头像 李华
网站建设 2026/9/11 7:47:17

ESP32蓝牙Beacon嵌入式测距实战:RSSI动态建模与精度优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 7:45:38

51单片机+ADS1110电子秤设计与实战

简介&#xff1a;本资源是一套完整的基于51单片机的电子秤设计开发包&#xff0c;面向嵌入式初学者、课程设计学生及单片机实践爱好者&#xff0c;解决重量检测系统从原理设计到仿真验证的全流程学习需求。资源共43个文件&#xff0c;涵盖Proteus仿真工程&#xff08;.pdsprj/.…

作者头像 李华