ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

freeCodeCamp 每日编程挑战题解:Challenge 93 Vowels and Consonants(统计字符串中的元音与辅音数量)

freeCodeCamp 每日编程挑战题解:Challenge 93 Vowels and Consonants(统计字符串中的元音与辅音数量) freeCodeCamp 每日编程挑战题解Challenge 93 Vowels and Consonants统计字符串中的元音与辅音数量【免费下载链接】freeCodeCampfreeCodeCamp.orgs open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp本篇围绕 freeCodeCamp 开源仓库中真实存在的每日编程挑战「Challenge 93: Vowels and Consonants」展开完整讲解这道题的需求定义、全部测试断言、起始代码与官方参考解法并从源码层面还原这道题目从 Markdown 题库文件出发经种子脚本写入数据库、再由后端接口下发到客户端答题页的完整生命周期。读完本文你既能独立用 JavaScript 实现一个「字符分类统计」函数也能理解 freeCodeCamp 每日编程挑战Daily Coding Challenge在仓库中的组织与运转方式。题目来源Challenge 93 在仓库中的位置这道题的原始挑战文件位于curriculum/challenges/english/blocks/daily-coding-challenges-javascript/68f6587287ad1f4ad39b0c7f.md与常规 freeCodeCamp 课程题不同每日编程挑战是一组按「天」发布的轻量算法题。Challenge 93 属于daily-coding-challenges-javascript区块该区块的元数据定义在 curriculum/structure/blocks/daily-coding-challenges-javascript.json 中isUpcomingChange: true该区块尚属于「即将上线的改动」用于正在开发中的新课程Dev Playground流程usesMultifileEditor: true题目在练习页使用多文件编辑器helpCategory: JavaScript帮助分类归属 JavaScriptdisableLoopProtectTests: true关闭循环保护相关测试其challengeOrder数组按顺序登记了 Challenge 1 至 Challenge 365其中本题目对应的记录为id: 68f6587287ad1f4ad39b0c7f、title: Challenge 93: Vowels and Consonants。每个挑战 Markdown 文件都遵守统一的 freeCodeCamp 挑战文档格式YAML frontmatter 记录id、title、challengeType、dashedName正文则由--description--题目描述、--hints--测试与提示、--seed--起始代码和--solutions--参考解法四个分节组成。Challenge 93 的全部内容都遵循这一骨架。需求拆解三种字符如何分类题目描述description原文如下Given a string, return an array with the number of vowels and number of consonants in the string.即给定一个字符串返回一个数组依次包含该字符串中元音的数量与辅音的数量。例如对Hello World应返回[3, 7]。题面进一步给出了三条分类规则是实现时必须严格遵守的边界条件规则内容元音Vowels由字母a、e、i、o、u组成任意大小写均算辅音Consonants由其余所有字母组成任意大小写均算非字母字符一律忽略不计入元音也不计入辅音从规则可以提炼出几个关键点大小写不敏感A与a都是元音H与h都是辅音因此统计前必须统一大小写非字母字符需要跳过空格、数字、标点如Hello, World!中的逗号和感叹号既不进入元音计数也不进入辅音计数返回顺序固定返回数组的第一个元素是元音数第二个元素才是辅音数顺序反了即使数字正确也无法通过断言。以示例Hello World手动推演一遍去掉中间的空格参与统计的字母为H e l l o W o r l d共 10 个其中元音为e、o、o共 3 个其余 7 个为辅音因此返回[3, 7]。测试断言题目给出的全部验收标准在挑战文件的--hints--分节中官方为count函数准备了 6 组测试用例全部使用assert.deepEqual进行严格深度比较数组逐元素相等才算通过count(Hello World)should return[3, 7]assert.deepEqual(count(Hello World), [3, 7]);count(JavaScript)should return[3, 7]assert.deepEqual(count(JavaScript), [3, 7]);count(Python)should return[1, 5]assert.deepEqual(count(Python), [1, 5]);count(freeCodeCamp)should return[5, 7]assert.deepEqual(count(freeCodeCamp), [5, 7]);count(Hello, World!)should return[3, 7]assert.deepEqual(count(Hello, World!), [3, 7]);count(The quick brown fox jumps over the lazy dog.)should return[11, 24]assert.deepEqual(count(The quick brown fox jumps over the lazy dog.), [11, 24]);将预期输出整理成速查表便于对照自查输入元音数辅音数返回值Hello World37[3, 7]JavaScript37[3, 7]Python15[1, 5]freeCodeCamp57[5, 7]Hello, World!37[3, 7]The quick brown fox jumps over the lazy dog.1124[11, 24]最后两组用例分别埋了两个「坑」Hello, World!验证标点符号会被忽略结果与没有标点的Hello World完全一致最后一句是经典的全字母短句pangram长度更长能有效检验循环统计在大字符串下不漏不重。起始代码你需要补全的函数契约挑战的--seed--分节给出了如下起始代码function count(str) { return str; }函数签名count(str)接收一个字符串参数模板默认直接返回原字符串显然不符合「返回形如[元音数, 辅音数]的数组」的契约。答题者的任务就是把函数体替换为真正的统计逻辑同时保证以下契约不变接收一个字符串str返回一个长度固定为 2 的数组[0]为元音数、[1]为辅音数空串或只含非字母字符的输入应返回[0, 0]测试虽未显式列出但这是规则的自然推论。官方参考解法与逐行分析挑战文件的--solutions--分节提供了如下参考实现function count(str) { const vowels aeiou; const consonants bcdfghjklmnpqrstvwxyz; let v 0, c 0; for (let i0; istr.length; i) { if (vowels.includes(str[i].toLowerCase())) { v; } if (consonants.includes(str[i].toLowerCase())) { c; } } return [v, c]; }这个解法的核心思路非常朴素且高效用两个常量字符串充当字符集合vowels收集 5 个元音字母consonants收集其余 21 个辅音字母。注意两串都按小写书写作为全量字符表的判据。逐字符遍历并把当前字符小写化str[i].toLowerCase()保证Hello中的大写H与元音表里的e能正确匹配实现「任意大小写」的规则。用String.prototype.includes做成员判定因为元音表只有 5 个字符、辅音表只有 21 个字符includes在常数长度的字符串上查找可视为 O(1)。两个相互独立的if而非if/else由于元音集合与辅音集合互斥每个字母至多命中其中一个分支而空格、数字、标点等非字母字符两个集合都匹配不上天然被忽略两个计数器都不递增。最后返回[v, c]严格遵循「元音在前、辅音在后」的输出契约。复杂度方面一次线性遍历循环内每次做常数次查找与自增因此时间复杂度为 O(n)n 为字符串长度额外空间为 O(1)。其它可行的实现思路除了官方解法还有多种写法都能通过全部 6 组断言且各有取舍方案一正则表达式分组统计function count(str) { const lower str.toLowerCase(); const vowels (lower.match(/[aeiou]/g) || []).length; const consonants (lower.match(/[bcdfghjklmnpqrstvwxyz]/g) || []).length; return [vowels, consonants]; }先把字符串整体转小写再分别用两个互补的字符类正则做全局匹配match返回数组的长度即对应计数|| []是为了规避match在无匹配时返回null导致取.length抛错的陷阱。注意不能直接把非字母字符交给/[a-z]/之外的正则再判断而是要让「不属于任一字母类」的字符自然地被两个字符类同时排除。方案二基于码点或集合的显式判断function count(str) { let v 0, c 0; for (const ch of str.toLowerCase()) { if (aeiou.includes(ch)) v; else if (ch a ch z) c; } return [v, c]; }先判断元音再用ch a ch z的字典序区间判断它是否属于 26 个英文字母从而把辅音与「非字母」区分开。这种写法把「忽略非字母」的逻辑显式表达了出来可读性更好。无论采用哪种方案只要保证三件事——大小写归一、非字母不计数、返回顺序为[元音, 辅音]——即可通过全部断言。另外提醒由于规则中「元音/辅音」仅限定为英文字母集合诸如带重音符号的外文字母不属于题目讨论范围会被一并忽略。题目的生命周期从 Markdown 题库到每日一题Challenge 93 这类题目并不只存在于课程文档中。freeCodeCamp 每日编程挑战有一个完整的「题库文件 → 数据库 → 后端接口 → 前端页面」流水线理解这条链路有助于把握题目在仓库中的真实角色。第一步题库文件与双语言配对每个每日挑战其实存在 JavaScript 与 Python 两个配套的 Markdown 挑战文件区块分别为daily-coding-challenges-javascript与daily-coding-challenges-python二者共享相同的题号、标题与题目描述仅在种子内容与测试语言上不同。区块元数据同样登记在 curriculum/structure/blocks/daily-coding-challenges-python.json 中。第二步种子脚本写入 MongoDB题库通过种子脚本写入数据库入口在 tools/daily-challenges/seed-daily-challenges.ts过程要点如下脚本假设主客户端正在本地运行GraphQL 端点为http://localhost:8000/___graphql并通过 tools/daily-challenges/helpers.ts 中的 GraphQL 查询按superBlock: dev-playground、block: daily-coding-challenges-javascript/daily-coding-challenges-python过滤出所有题目脚本硬校验期望的题目总数EXPECTED_CHALLENGE_COUNT 365同时要求 JavaScript 与 Python 两组题目的数量、标题、描述、测试数一一对应否则直接抛错中止起始日期常量定义为START_DATE 2025-08-11UTC脚本把第 i 道题0 基的date设为START_DATE i × 24h并把challengeNumber设为i 1。按此公式推算Challenge 93i 92对应的发布日期为2025-11-11。脚本源码注释特别强调该日期上线后不可再改动否则会破坏已发布的每日节奏写入时以题目 id 作为 MongoDB_id对已存在记录执行replaceOneupsert实现可重复幂等导入运行方式参见 tools/daily-challenges/README.md先配置好.env、以开启 upcoming changes 的方式启动主客户端再在tools/daily-challenges目录执行pnpm seed-daily-challenges。导入后每条记录形如challengeNumber、title去掉Challenge N:前缀、date、description以及javascript/python两个语言分支每个分支内含tests由texttestString组成与challengeFiles由contentsfileKey组成。Challenge 93 文件--hints--分节里的每条assert.deepEqual(...)最终都会成为数据库记录中tests[].testString的一部分--seed--中的function count(str)则会进入challengeFiles[].contents。第三步Prisma 数据模型与用户完成记录数据库层的字段结构定义在 api/prisma/schema.prisma 中。核心模型为DailyCodingChallenges包含challengeNumber、date、title、description与内嵌的javascript/python两个DailyCodingChallengeApiLanguage类型各含tests与challengeFiles两个子集合。同一文件还定义了CompletedDailyCodingChallenge复合类型记录了用户已完成题目按题目 id 关联的completedDate与所用languagesjavascript/python枚举即用户答题进度的落库结构。第四步后端接口下发每日题目接口位于 api/src/daily-coding-challenge/routes/daily-coding-challenge.ts该插件提供一组公开的 GET 路由路由作用/daily-coding-challenge/date/:date按YYYY-MM-DD日期取当天题目/daily-coding-challenge/day/:day按MM-DD取某日题目会换算到对应的年份源日期/daily-coding-challenge/today取美国中部时间US Central今天的题目/daily-coding-challenge/month/:month按YYYY-MM取整月题目的简要列表只含 id、challengeNumber、date、title/daily-coding-challenge/all列出全部已发布题目简要信息/daily-coding-challenge/newest返回最新一道题的日期其中若干实现细节与 Challenge 93 直接相关路由会把「日期晚于美国中部时间当天零点」的题目视为未发布并返回 404参数格式由 api/src/daily-coding-challenge/schemas/daily-coding-challenge.ts 中的 TypeBox 模式如date字段要求YYYY-MM-DD、day字段要求^\d{2}-\d{2}$、month字段要求^\d{4}-\d{2}$先行校验。此外 api/src/daily-coding-challenge/README.md 明确说明该组接口只负责下发题目信息题目的提交与判定仍走主挑战完成路由。第五步前端作答与每日互动在前端每日挑战拥有专属的作答页面与组件。页面位于 client/src/client-only-routes/show-daily-coding-challenge.tsx题目挂件与月份日历组件分别位于 client/src/components/daily-coding-challenge/widget.tsx 与同目录的calendar.tsx。用户在日历上选中 Challenge 93 当天后页面拉取该题数据、展示题目描述与种子代码借助多文件编辑器完成count函数的编写并交给随题下发的testString逐条执行断言判定通过与否。该流程还有端到端测试覆盖位于 e2e/daily-coding-challenge.spec.ts。小结与自查清单Challenge 93 是一道典型的「字符串扫描 字符分类」入门算法题训练点是对单字符的小写归一处理、基于固定字符集合的成员判断、以及针对非字母输入的边界兜底。在 freeCodeCamp 仓库中它同时是每日编程挑战内容管线的一个实例——从 Markdown 题库文件出发经种子脚本写入数据库、由 Prisma 建模、再由后端按日期下发、最终在前端每日页面上作答并留痕到用户完成记录。动手前可对照这份清单自查实现是否正确大写字母如Hello的H、freeCodeCamp中的大写C是否被正确计入空格、数字、标点如Hello, World!是否被忽略返回值是否是[元音数, 辅音数]的顺序空字符串是否返回[0, 0]能否通过 题目文件--hints--中的全部 6 组assert.deepEqual断言。【免费下载链接】freeCodeCampfreeCodeCamp.orgs open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表