尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

freeCodeCamp 每日编程挑战解析:Coffee Order Parser(Challenge 278)字符串菜单解析实战

freeCodeCamp 每日编程挑战解析:Coffee Order Parser(Challenge 278)字符串菜单解析实战 freeCodeCamp 每日编程挑战解析Coffee Order ParserChallenge 278字符串菜单解析实战【免费下载链接】freeCodeCampfreeCodeCamp.orgs open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp本篇技术指南聚焦 freeCodeCamp 开源课程体系中daily-coding-challenges-javascript模块的Challenge 278: Coffee Order Parser。这是一个典型的从自然语言订单字符串中识别菜单项、计算总价并格式化输出的字符串解析编程题。读完本文你将掌握该挑战的完整题目约束、菜单定价规则、官方参考实现以及它在 freeCodeCamp 每日编程挑战机制中的落地位置包括课程块配置、API 路由与数据校验并能够独立写出通过全部测试用例的解法。挑战题目与输入输出约定该挑战定义在 curriculum/challenges/english/blocks/daily-coding-challenges-javascript/69e2383af7832c8032603b94.md 中挑战类型为challengeType: 28每日编码挑战dashedName 为challenge-278。题目要求给定一个咖啡订单字符串识别其中出现的所有菜单项并返回格式化后的订单。核心输入输出约定如下输入是一个任意自然语言句子例如Id like an oat latte with vanilla syrup and an extra shot please.输出是一个字符串匹配到的菜单项按菜单中出现的顺序用 连接后跟: 冒号加空格和总价总价始终保留两位小数并带美元符号前缀例如$6.25。菜单定价表题目规定的唯一数据源匹配必须以如下菜单为基准共 9 个条目| Item | Price | | - | - | |cold brew| $4.50 | |oat latte| $5.00 | |cappuccino| $4.75 | |espresso| $3.00 | |vanilla syrup| $0.75 | |caramel drizzle| $0.60 | |extra shot| $0.50 | |oat milk| $0.75 | |cream| $0.75 |注意几个细节菜单项大多是多词词组如vanilla syrup、caramel drizzle因此匹配时不能简单按空格分词而应使用子串包含判断oat milk与oat latte都以oat开头但它们是两个独立条目匹配时必须使用完整词组输出顺序以菜单表的行顺序为准cold brew → oat latte → cappuccino → espresso → vanilla syrup → caramel drizzle → extra shot → oat milk → cream而不是订单字符串中出现的先后顺序。这一点在 Hint 3 与 Hint 5 中体现得很明显见下文。官方测试用例--hints--逐条解析题目附带了 5 个通过assert.equal验证的测试用例构成判断解法的唯一标准。逐一拆解Hint 1多词组 自然语言冗余词assert.equal(formatCoffeeOrder(Id like an oat latte with vanilla syrup and an extra shot please.), oat latte vanilla syrup extra shot: $6.25);匹配oat latte$5.00vanilla syrup$0.75extra shot$0.50合计 $6.25。句子中的Id like、with、and、please等均为噪音词需被忽略。菜单顺序下vanilla syrup排在extra shot之前因此输出为oat latte vanilla syrup extra shot。Hint 2验证菜单表顺序而非输入顺序assert.equal(formatCoffeeOrder(Give me a cappuccino with caramel drizzle, vanilla syrup, and some oat milk.), cappuccino vanilla syrup caramel drizzle oat milk: $6.85);输入顺序是caramel drizzle → vanilla syrup → oat milk但输出为cappuccino vanilla syrup caramel drizzle oat milk——这正是菜单表中vanilla syrup第 5 行先于caramel drizzle第 6 行的体现。总价$4.75 $0.75 $0.60 $0.75 $6.85。Hint 3单品订单assert.equal(formatCoffeeOrder(Just an espresso please.), espresso: $3.00);只匹配到一个条目时没有 连接符直接输出espresso: $3.00。同时验证了价格必须带两位小数$3.00而非$3。Hint 4四条目的复杂组合assert.equal(formatCoffeeOrder(Ill take an oat latte with cream and an extra shot, and some vanilla syrup and caramel drizzle.), oat latte vanilla syrup caramel drizzle extra shot cream: $7.60);五个匹配项按菜单顺序排列oat latte→vanilla syrup→caramel drizzle→extra shot→cream。总价 $5.00 $0.75 $0.60 $0.50 $0.75 $7.60。Hint 5大小写不敏感所有 Hint 中菜单项都以小写形式嵌入句子但题目要求解法在匹配时使用toLowerCase()归一化确保用户输入大写如Oat Latte时同样可以命中。这一点在官方解法的order.toLowerCase().includes(item.name)中有明确体现。起始代码--seed--与官方参考解法--solutions--挑战提供的起始函数seed仅返回原字符串需要你补全逻辑function formatCoffeeOrder(order) { return order; }官方参考解法完整保留如下function formatCoffeeOrder(order) { const menu [ { name: cold brew, price: 4.50 }, { name: oat latte, price: 5.00 }, { name: cappuccino, price: 4.75 }, { name: espresso, price: 3.00 }, { name: vanilla syrup, price: 0.75 }, { name: caramel drizzle, price: 0.60 }, { name: extra shot, price: 0.50 }, { name: oat milk, price: 0.75 }, { name: cream, price: 0.75 }, ]; const found menu.filter(item order.toLowerCase().includes(item.name)); const total found.reduce((sum, item) sum item.price, 0); return ${found.map(item item.name).join( )}: $${total.toFixed(2)}; }该解法蕴含三个关键技术点值得单独拆解1.Array.prototype.filter实现菜单序过滤menu.filter(item order.toLowerCase().includes(item.name))天然保留了menu数组的原始顺序——这正是题目要求按菜单顺序输出的最简洁实现方式。includes是子串包含判断天然适配多词词组匹配。2.reduce完成价格累加found.reduce((sum, item) sum item.price, 0)以 0 为初始值遍历已匹配项累加价格。需要注意浮点累加的场景由于使用了toFixed(2)进行最终格式化0.1 0.2这类二进制浮点误差不会影响输出例如 $4.75 $0.75 $5.50 在浮点下可能为 5.499999...但toFixed(2)会正确舍入为5.50。3.toFixed(2)保证两位小数total.toFixed(2)是满足总价始终保留两位小数的关键。(3).toFixed(2)返回3.00(6.25).toFixed(2)返回6.25与题目要求的$前缀拼接后即得到最终输出。挑战在课程体系中的位置与运行机制课程块配置300 道挑战的排期表该挑战的 id69e2383af7832c8032603b94收录在 curriculum/structure/blocks/daily-coding-challenges-javascript.json 的challengeOrder中序号为 Challenge 278。该课程块block的完整配置如下{ isUpcomingChange: true, dashedName: daily-coding-challenges-javascript, usesMultifileEditor: true, helpCategory: JavaScript, disableLoopProtectTests: true, blockLayout: legacy-challenge-list, challengeOrder: [ ... ] }值得注意的配置项usesMultifileEditor: true——该模块启用多文件编辑器挑战在客户端可编辑运行disableLoopProtectTests: true——禁用循环保护测试允许解题者自由编写while/for等循环逻辑而不被注入的防护代码干扰helpCategory: JavaScript——归类到 JavaScript 帮助频道。整个 block 从 Challenge 1Vowel Balance到 300涵盖字符串处理、数组操作、算法、日期计算、Markdown 解析等主题Coffee Order Parser 是其中偏实战字符串解析的一道。每日挑战的 API 支撑从日期到题目的下发链路课程块中的挑战并非一次性全部展示而是按每日一题节奏下发。后端实现位于 api/src/daily-coding-challenge/routes/daily-coding-challenge.ts该 Fastify 插件暴露了以下只读 GET 端点| 路由 | 说明 | | - | - | |/daily-coding-challenge/today| 返回当天美国中部时间的挑战 | |/daily-coding-challenge/date/:date| 按YYYY-MM-DD精确日期查询 | |/daily-coding-challenge/day/:day| 按MM-DD月-日查询会映射到原始挑战周期 | |/daily-coding-challenge/month/:month| 按YYYY-MM返回整月挑战列表 | |/daily-coding-challenge/all| 返回全部已发布的挑战列表 | |/daily-coding-challenge/newest| 返回最新挑战日期 |其中/daily-coding-challenge/date/:date会校验日期格式dateStringToUtcMidnight要求YYYY-MM-DD并拒绝返回晚于今天美国中部时间的挑战防止题目提前泄露。请求量统计通过fastify.Sentry?.metrics?.count上报如dcc.challenge_viewed、dcc.challenge_not_found。时区与挑战周期的源码级细节日期计算集中在 api/src/daily-coding-challenge/utils/helpers.ts几个关键函数getNowUsCentral()通过date-fns-tz的getTimezoneOffset(America/Chicago, ...)将当前时间折算到美国中部时区保证今日挑战以 US Central 为准getUtcMidnight(date)把任意 Date 归一到 UTC 零点避免时间分量干扰日期比较getSourceDate(date)单年循环映射。原始挑战周期为 2025-08-11 至 2026-08-10一年 366 天getSourceDate将任意日期映射回该周期内对应的一天——例如 2 月 29 日请求会被映射到 2 月 28 日的挑战。这解释了为何每日挑战可以年复一年复用同一批题目。客户端入口与数据校验前端侧首页与学习地图通过 client/src/components/daily-coding-challenge/widget.tsx 中的DailyCodingChallengeWidget渲染今日挑战与历史归档入口链接形如/learn/daily-coding-challenge/${getMonthDayUsCentral()}getMonthDayUsCentral实现在 client/src/components/daily-coding-challenge/helpers.ts返回MM-DD格式。从数据库取出的挑战数据需通过 client/src/utils/daily-coding-challenge-validator.ts 的 Joi 校验每条挑战必须包含id、challengeNumber正整数、title、date、description以及javascript/python两套语言数据每套语言数据又包含tests含text与testString与challengeFiles含fileKey与contents。Challenge 278 的 5 个 Hint 就是tests中testString部分的体现——它们以assert.equal的形式注入浏览器端运行环境由测试框架执行判定。独立实现一版可运行的完整解法在不查看官方解法的情况下你也可以基于题目约束独立实现。下面给出一个带注释、可复制运行的完整版本与官方解法等价但补充了边界处理说明function formatCoffeeOrder(order) { // 1. 以菜单表顺序定义数据源顺序即输出顺序 const menu [ { name: cold brew, price: 4.5 }, { name: oat latte, price: 5.0 }, { name: cappuccino, price: 4.75 }, { name: espresso, price: 3.0 }, { name: vanilla syrup, price: 0.75 }, { name: caramel drizzle, price: 0.6 }, { name: extra shot, price: 0.5 }, { name: oat milk, price: 0.75 }, { name: cream, price: 0.75 } ]; // 2. 归一化大小写后做子串匹配filter 天然保持菜单顺序 const lowerOrder order.toLowerCase(); const found menu.filter(item lowerOrder.includes(item.name)); // 3. 累加总价 const total found.reduce((sum, item) sum item.price, 0); // 4. 按 A B C: $x.xx 格式输出toFixed(2) 保证两位小数 return ${found.map(item item.name).join( )}: $${total.toFixed(2)}; } // 验证全部 5 个官方用例 console.log(formatCoffeeOrder(Id like an oat latte with vanilla syrup and an extra shot please.)); // oat latte vanilla syrup extra shot: $6.25 console.log(formatCoffeeOrder(Give me a cappuccino with caramel drizzle, vanilla syrup, and some oat milk.)); // cappuccino vanilla syrup caramel drizzle oat milk: $6.85 console.log(formatCoffeeOrder(Can I get a cold brew with some cream and an extra shot.)); // cold brew extra shot cream: $5.75 console.log(formatCoffeeOrder(Just an espresso please.)); // espresso: $3.00 console.log(formatCoffeeOrder(Ill take an oat latte with cream and an extra shot, and some vanilla syrup and caramel drizzle.)); // oat latte vanilla syrup caramel drizzle extra shot cream: $7.60在浏览器或 Node.js 环境中逐条运行输出应与右侧注释完全一致。常见误区与边界条件总结基于 5 个 Hint 和官方解法解题时应特别规避以下误区按空格 split 后匹配单词多词菜单项vanilla syrup会被拆散导致漏匹配。正确做法是整串子串包含判断按输入顺序输出Hint 2、Hint 4 明确要求按菜单表顺序而非句子中出现顺序价格格式缺失$3.00不能写成$3必须使用toFixed(2)忽略大小写匹配前必须toLowerCase()归一化否则用户输入Oat Latte会失败输出格式细节连接符是 加号两侧各一个空格分隔符是: 冒号后一个空格多一个少一个空格都会导致assert.equal失败。进一步阅读与验证入口挑战原文含题目、菜单表、5 个 Hint、seed 与 solutioncurriculum/challenges/english/blocks/daily-coding-challenges-javascript/69e2383af7832c8032603b94.md课程块排期与配置curriculum/structure/blocks/daily-coding-challenges-javascript.json后端每日挑战路由与端点api/src/daily-coding-challenge/routes/daily-coding-challenge.ts日期/时区/周期映射工具api/src/daily-coding-challenge/utils/helpers.ts后端路由测试api/src/daily-coding-challenge/routes/daily-coding-challenge.test.ts客户端数据校验client/src/utils/daily-coding-challenge-validator.ts每日挑战组件入口client/src/components/daily-coding-challenge/widget.tsx端到端测试覆盖每日挑战页面交互e2e/daily-coding-challenge.spec.ts通过本文你不仅掌握了 Challenge 278 的完整解题思路与官方实现细节还理解了这道题在 freeCodeCamp 每日编程挑战体系中的运行链路——从课程块的排期配置到后端按美国中部时间下发题目再到客户端渲染与 Joi 数据校验。以此为基础你可以轻松举一反三完成同一模块中其他字符串解析类挑战如 CSV Header Parser、Markdown Bold Parser 等。【免费下载链接】freeCodeCampfreeCodeCamp.orgs open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表