资讯动态

freeCodeCamp 每日编程挑战:Character Count(字符计数)算法实现全解析

发布时间:2026/9/9 21:04:49 来源:尧图企业网站定制
freeCodeCamp 每日编程挑战Character Count字符计数算法实现全解析【免费下载链接】freeCodeCampfreeCodeCamp.orgs open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCampfreeCodeCamp 开源仓库中的「每日编程挑战Daily Coding Challenges」系列为学习者提供每天一道、可自动化验证的短算法题。本文以该系列的第 105 道题Character Count为研究对象完整解读其题目约束、三组测试断言、官方推荐解法与逐行原理并顺着仓库源码追溯这道练习题从 Markdown 题目文件、GraphQL 数据流到 MongoDB 每日挑战集合的完整流转链路帮助读者既掌握算法本身也理解 freeCodeCamp 课程题目的真实运作方式。一、挑战文件与所属模块定位本题对应的原始 Markdown 文件位于curriculum/challenges/english/blocks/daily-coding-challenges-javascript/68ffb91507a5b645769328c8.md其 frontmatter 记录了题目的关键元数据--- id: 68ffb91507a5b645769328c8 title: Challenge 105: Character Count challengeType: 28 dashedName: challenge-105 ---其中challengeType: 28并非随意编号。在 packages/shared/src/config/challenge-types.ts 中可以看到完整的类型枚举const dailyChallengeJs 28; const dailyChallengePy 29;也就是说类型28被明确定义为JavaScript 每日编程挑战dailyChallengeJs29则是其 Python 姊妹题类型。这一常量随后被导入 challengeTypes 对象并被类似getIsDailyCodingChallenge()见同文件 L187-L188的辅助函数用于判断某个挑战是否属于每日挑战体系。本题归属于 superblock「Dev Playground」下的 blockdaily-coding-challenges-javascript。该 block 的编排元数据在 curriculum/structure/blocks/daily-coding-challenges-javascript.json 中定义其中可以确认该 block 启用了多文件编辑器usesMultifileEditor: true、帮助分类为 JavaScript、并按legacy-challenge-list布局排列文件末尾的challengeOrder中可找到本题id: 68ffb91507a5b645769328c8对应 Challenge 105以及从 Challenge 1 到 200 的完整题目清单方便横向对比同类题目的难度递进。二、题目描述与五条硬性验收规则题目原文非常精炼给定一个句子字符串返回一个数组数组元素按字母顺序给出每个字符出现的次数。其# --description--段落下还给出了五条必须同时满足的约束统计时把大写和小写字母视为同一个字母大小写不敏感。忽略数字、空格、标点等所有非字母字符。返回元素采用letter count的格式例如a 3。返回的字母必须全部是小写。未在给定字符串中出现的字母不得出现在返回结果中。换言之这是一个大小写归一化 白名单过滤 频率统计 字典序排序 格式化输出的复合小算法题。每一道约束都直接对应一条测试用例缺一不可。三、三组测试用例逐条推演挑战文件# --hints--部分给出了三组断言全部通过assert.deepEqual对返回数组做严格深比较。下面逐条手动推演验证我们对题意的理解。3.1countCharacters(hello world)assert.deepEqual(countCharacters(hello world), [d 1, e 1, h 1, l 3, o 2, r 1, w 1]);去掉空格后字母为h e l l o w o r l d。逐字母统计字母helowrd次数1132111按字母序d e h l o r w拼接为[d 1, e 1, h 1, l 3, o 2, r 1, w 1]与断言一致。3.2countCharacters(I love coding challenges!)assert.deepEqual(countCharacters(I love coding challenges!), [a 1, c 2, d 1, e 3, g 2, h 1, i 2, l 3, n 2, o 2, s 1, v 1]);该用例重点考察两件事首字母I大写被当作小写i统计以及末尾感叹号!与中间空格被忽略。实测i出现 2 次I与coding中的il出现 3 次全部字母统计后排序列出即得上述结果。3.3countCharacters(// TODO: Complete this challenge ASAP!)assert.deepEqual(countCharacters(// TODO: Complete this challenge ASAP!), [a 3, c 2, d 1, e 4, g 1, h 2, i 1, l 3, m 1, n 1, o 3, p 2, s 2, t 3]);这是最具代表性的一例字符串里既有//、:、!这样的标点符号又有TODO、ASAP等全大写单词。斜杠、冒号、感叹号全部被过滤T、A、S分别按t、a、s归入统计最终a恰好 3 次challenge、ASAP中两次 challenge中一次说明大小写折叠与标点过滤必须同时正确实现。四、官方推荐解法逐行拆解挑战文件# --solutions--段给出了参考答案function countCharacters(sentence) { const counts {}; for (let char of sentence.toLowerCase()) { if (char a char z) { counts[char] (counts[char] || 0) 1; } } return Object.keys(counts) .sort() .map(letter ${letter} ${counts[letter]}); }这段代码只有四步却恰好逐一命中题目全部约束。第一步大小写归一化。sentence.toLowerCase()在遍历之前一次性完成任何大写字母在进入循环前已经被折叠成小写。这正是第 1、4 条约束大小写同计、输出全小写的实现基础。第二步白名单过滤与统计。for...of逐字符取出接着if (char a char z)做字符区间判断。由于此时char已全部是小写任何数字、空格、标点、斜杠的码位都不落在a~z闭区间内从而被自然排除对应第 2 条约束。统计行counts[char] (counts[char] || 0) 1;是经典的计数惯用法若counts[char]已存在则取其值、否则取0再加一后写回。利用对象键的唯一性天然完成了“每种字母只统计一次、且不存在的字母不会产生记录”——同时满足第 5 条约束。第三步字典序排序。Object.keys(counts).sort()返回所有被统计到的字母键并排序。这里存在一个值得注意的细节Array.prototype.sort()的默认比较器是把元素转成字符串后按Unicode 码点做字典序比较而不是数值比较。正因本题的 key 都是单个小写字母字典序恰好等于字母序所以省略比较函数是安全的。第四步格式化输出。.map(letter \${letter} ${counts[letter]})用模板字符串将字母与次数以空格连接生成诸如l 3的最终元素精确符合第 3 条约束要求的letter count 格式。而学习者最初拿到的# --seed--代码只是一个空壳function countCharacters(sentence) { return sentence; }即函数默认原样返回句子字符串与期望的数组格式差距巨大——这是刻意设计要求你从零补全整套计数与格式化逻辑。五、边界条件与易错点清单空字符串遍历不会产生任何键Object.keys([])为空返回[]符合“不统计未出现字母”的语义。只含非字母字符如123 !?//同样返回空数组过滤逻辑需要正确处理。排序方向默认.sort()是字典序而非数值序若把字母换成多位数统计例如单词频率题就必须显式传(a, b) counts[a] - counts[b]之类比较器本题因为是单字符 key 所以可省略。字符区间判断的先后依赖char a char z之所以成立前提是已经在循环前执行过toLowerCase()若先判断再转小写大写字母会被误过滤。不要用charCodeAt硬编码记忆 ASCII 值虽然charCodeAt()也能实现97–122 为小写字母区间但字符串比较写法可读性更好、也更贴近题目“忽略大小写”的自然语言描述。时间复杂度单遍遍历为 O(n)排序为 O(k log k)k 为不同字母数上限 26对普通句子规模完全够用。六、本地动手验证三种方式由于仓库是只读的验证思路是把下面任一方案放到自己的临时环境中运行。方式一Node 单文件快速验证。将官方解法保存为countCharacters.js在末尾追加console.log(countCharacters(hello world)); console.log(countCharacters(I love coding challenges!)); console.log(countCharacters(// TODO: Complete this challenge ASAP!));用node countCharacters.js运行逐一核对输出是否与题目的三组期望数组一致。方式二断言测试。直接复用题目中的assert.deepEqual断言const assert require(node:assert); // ...countCharacters 实现... assert.deepEqual(countCharacters(hello world), [d 1, e 1, h 1, l 3, o 2, r 1, w 1]); assert.deepEqual(countCharacters(I love coding challenges!), [a 1, c 2, d 1, e 3, g 2, h 1, i 2, l 3, n 2, o 2, s 1, v 1]); assert.deepEqual(countCharacters(// TODO: Complete this challenge ASAP!), [a 3, c 2, d 1, e 4, g 1, h 2, i 1, l 3, m 1, n 1, o 3, p 2, s 2, t 3]); console.log(all tests passed);方式三浏览器控制台。freeCodeCamp 的每日挑战在网页端通过带测试字符串的题目渲染运行该题的--hints--本质上正是把assert.deepEqual(...)这类断言放进每道题关联的tests中执行。读者亦可在任意现代浏览器开发者工具中粘贴函数与断言完成验证。七、追根溯源这份题目文件如何变成线上挑战Character Count 远不止是纯文本练习题它在仓库里是一条完整数据管线的起点。理解这条链路能帮助你更清楚地把握该文档的工程意义内容源题目正文与断言存在于curriculum/challenges/english/blocks/daily-coding-challenges-javascript/目录的 Markdown 文件中本题即 68ffb91507a5b645769328c8.md并被结构 JSON 的challengeOrder按顺序登记。课程构建与 GraphQL 暴露block 归属的「Dev Playground」superblock 被前端 Gatsby 客户端以“即将上线的变更upcoming changes”方式构建从而能在本地 GraphQL 端点查询到这些挑战。种子脚本抓取tools/daily-challenges/helpers.ts 中的fetchChallenges()会向http://localhost:8000/___graphql发起查询过滤条件正是superBlock: dev-playground、block: daily-coding-challenges-javascript并把题目字段id、title、description、tests、challengeFiles映射为结构化的 challenge 对象——其中tests即本文第三部分所展示的那些断言字符串。跨语言配对与入库combineChallenges()会把同日期的 JavaScript 与 Python 两道题合并校验标题、描述、测试数量都必须一致随后写入 MongoDB 的DailyCodingChallenges集合整个操作由 tools/daily-challenges/README.md 与 tools/daily-challenges/seed-daily-challenges.ts 说明和驱动运行命令为pnpm seed-daily-challenges前置条件是复制sample.env为.env、装好依赖并让主客户端以显示 upcoming changes 的方式运行。运行时判定线上的每日挑战界面与后端 API 位于 client/src/client-only-routes/show-daily-coding-challenge.tsx客户端展示以及api/src/daily-coding-challenge/相关路由与 schema 中最终依据入库的测试断言对用户提交的代码逐条验证。也就是说你在 curriculum/challenges/english/blocks/daily-coding-challenges-javascript/68ffb91507a5b645769328c8.md 里看到的每一条断言最终都会成为生产环境数据库中真正被执行、用来判定千万学习者答案正确与否的测试字符串。八、举一反三在同一 block 中精进同类技能Character Count 所在 block 的同批次题目恰好构成了一条循序渐进的练习线索值得对照训练Challenge 100: 100 Characters 同样围绕字符/字母处理Challenge 128: Consonant Count列表中存在该条目把“统计”细化到辅音子集Challenge 35: Word Frequency 将计数对象从“字符”升级为“单词”此时排序就必须注意多字符 key 的字典序细节——正好呼应本文第五节提醒的比较器问题若想挑战同主题的难度升级版Challenge 200: Letter and Number Count 则要求把数字纳入统计属于对该题过滤逻辑的直接扩展。小结Character Count 是一道表面简单、实则覆盖了字符串归一化、字符白名单过滤、哈希计数、排序与格式化输出的高密度入门算法题。通过本文你可以按五条约束读懂并复现官方解法用它对照三组断言做本地验证同时借助challengeType: 28、Dev Playground 与seed-daily-challenges脚本理解它如何从一份 Markdown 变成真实可判题的每日挑战。掌握此题后其背后的“过滤 计数 排序输出”三板斧可以直接迁移到词频统计、字符去重、匿名化脱敏等大量日常编码场景。【免费下载链接】freeCodeCampfreeCodeCamp.orgs open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价