news 2026/9/10 20:06:44

freeCodeCamp 每日编码挑战解析:Challenge 179 Pocket Change 零钱统计与美元格式化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
freeCodeCamp 每日编码挑战解析:Challenge 179 Pocket Change 零钱统计与美元格式化

freeCodeCamp 每日编码挑战解析:Challenge 179 Pocket Change 零钱统计与美元格式化

【免费下载链接】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 179 "Pocket Change" 是一道练习数组归约、整数除法与字符串格式化基本功的 JavaScript 题目:给定口袋中若干硬币的面值数组(单位为美分),返回格式化后的美元金额字符串。本文以 挑战原文 为骨架,结合该仓库中挑战的题目结构、API 路由与 Prisma 数据模型实现,完整讲解题面、测试用例、解题思路与官方解答,并延伸说明这类挑战在 freeCodeCamp 中的运行机制。

题目要求:把美分数组换算成"$D.CC"字符串

Challenge 179 的题面非常简洁:

Given an array of integers representing the coins in your pocket, with each integer being the value of a coin in cents, return the total amount in the format"$D.CC".

即:函数countChange(change)接收一个整数数组,每个元素代表口袋里一枚硬币的面值(单位:美分),需要返回总金额,格式为"$D.CC"

题面附带两条明确约束:

  • 100 美分 = 1 美元:需要以 100 为基数完成美元与美分之间的换算。
  • 输出格式严格要求:金额不足 1 美元时,美元部分要包含前导零(如"$0.41");美分部分必须始终是恰好两位数字(如"$0.05"而不是"$0.5")。

这两个约束正是本道题的考察重点:Math.floor取整拆分、取模运算求余数,以及padStart补齐位数。

全部测试用例:6 组输入与期望输出

原文档在--hints--区块中给出了 6 组测试断言,覆盖了不同规模的数组、跨美元边界、整数美元等典型场景:

输入数组期望返回值用例覆盖点
[25, 10, 5, 1]"$0.41"不足 1 美元时美元部分带前导零
[25, 10, 5, 1, 25, 10, 25, 1, 1, 10, 5, 25]"$1.43"跨过 1 美元边界,总美分 > 100
[100, 25, 100, 1000, 5, 500, 2000, 25]"$37.55"大面值(1000、2000 美分)混入
[10, 5, 1, 10, 1, 25, 1, 1, 5, 1, 10]"$0.70"再次验证前导零与个位美分
[1]"$0.01"单枚最小硬币,美分补零
[25, 25, 25, 25]"$1.00"恰好整美元,美分为"00"

最后一组[25, 25, 25, 25]是最容易被忽略的边界情况:总和为 100 美分,期望输出是"$1.00"而非"$1",直接验证了"美分始终恰好两位"的硬性要求。

测试用例在挑战文件中以assert.equal(...)形式给出,例如:

assert.equal(countChange([25, 10, 5, 1]), "$0.41"); assert.equal(countChange([25, 25, 25, 25]), "$1.00");

解题思路拆解:三步走

把问题拆成三个步骤即可得到清晰解法:

  1. 累加总美分:用Array.prototype.reduce把数组所有硬币面值求和,得到totalCents
  2. 拆分美元与美分Math.floor(totalCents / 100)得到完整美元数dollarstotalCents % 100得到剩余美分数cents
  3. 格式化输出:美元部分直接拼入模板字符串;美分部分用String.prototype.padStart(2, "0")保证至少两位,不足两位时补前导零。

其中第 3 步是本题的得分关键:cents.toString().padStart(2, "0")同时处理了"0.01"(个位美分补零)与"1.00"(整美元时美分为"00")两种情况。

官方解答:reduce + Math.floor + padStart 的经典组合

原文档在--solutions--区块中给出了官方解答:

function countChange(change) { const totalCents = change.reduce((sum, coin) => sum + coin, 0); const dollars = Math.floor(totalCents / 100); const cents = totalCents % 100; return `$${dollars}.${cents.toString().padStart(2, "0")}`; }

逐行解读:

  • change.reduce((sum, coin) => sum + coin, 0):以0为初始值累加所有面值,不依赖数组是否排序、是否包含大面值。
  • Math.floor(totalCents / 100)Math.floor对正整数取整,结果就是完整美元数。
  • totalCents % 100:取模得到不足 1 美元的零头美分。
  • `$${dollars}.${cents.toString().padStart(2, "0")}`:模板字符串拼出"$D.CC"padStart(2, "0")保证美分恰好两位。

官方解答没有使用toFixed(2)。原因在于toFixed处理的是小数而非"整数美分拆分",而这里先以整数运算完成换算,再用字符串补齐位数,从根本上避免了浮点精度问题——这也是金额计算始终用整数美分、避免浮点运算这一良好实践在入门题中的体现。

拓展解法:多种等价实现

除官方解答外,还有几种思路等价、可读性同样良好的实现:

方案一:Math.trunc替代Math.floor(正整数场景等价)

function countChange(change) { const totalCents = change.reduce((sum, coin) => sum + coin, 0); const dollars = Math.trunc(totalCents / 100); const cents = totalCents % 100; return `$${dollars}.${cents.toString().padStart(2, "0")}`; }

本题输入均为正整数,Math.truncMath.floor结果一致;若输入可能为负则二者有差异,但本挑战不涉及。

方案二:toFixed(2)写法(不建议)

function countChange(change) { const totalCents = change.reduce((sum, coin) => sum + coin, 0); return `$${(totalCents / 100).toFixed(2)}`; }

虽然对当前测试用例也能通过,但依赖浮点除法与toFixed的四舍五入规则,在真实金额计算场景下存在精度隐患,不如纯整数运算稳妥。

方案三:手写补零逻辑

function countChange(change) { const totalCents = change.reduce((sum, coin) => sum + coin, 0); const dollars = Math.floor(totalCents / 100); const cents = totalCents % 100; const centsStr = cents < 10 ? `0${cents}` : `${cents}`; return `$${dollars}.${centsStr}`; }

用条件判断替代padStart,原理相同,适合想刻意练习字符串拼接的读者。

这道题在 freeCodeCamp 中的运行机制

Challenge 179 属于"每日编码挑战"(Daily Coding Challenge)JavaScript 系列。它的题目编号、难度等级与展示方式可以从仓库中的几处结构配置得到印证。

挑战文件本身的结构

挑战文件 69738771fb5a7b8b24cca2a5.md 的 frontmatter 定义了:

  • id: 69738771fb5a7b8b24cca2a5:全局唯一的挑战 ID;
  • title: "Challenge 179: Pocket Change":系列内序号与标题;
  • challengeType: 28:28 对应"每日编码挑战"这一特殊类型;
  • dashedName: challenge-179:用于生成 URL 的短名称。

正文则按--description--(题面)、--hints--(断言)、--seed--(种子代码)、--solutions--(官方解答)四个区块组织,这一模板与 freeCodeCamp 所有挑战的标准格式一致。

所在区块与课程编排

该文件位于daily-coding-challenges-javascript区块目录下,区块的编排信息记录在 daily-coding-challenges-javascript.json 中:

  • challengeOrderidtitle列出了从 Challenge 1 到 Challenge 200+ 的全部题目,Challenge 179 "Pocket Change" 的id与 frontmatter 完全对应,说明该题目已正式纳入课程顺序;
  • helpCategory: "JavaScript":题目归属于 JavaScript 帮助分类;
  • blockLayout: "legacy-challenge-list":区块采用经典挑战列表布局;
  • usesMultifileEditor: true:支持多文件编辑器;
  • isUpcomingChange: true:该区块当前标记为"即将上线的变更",表明它仍处于开发/预览阶段。

服务端数据模型与 API

每日编码挑战并非直接在浏览器端读取上面的 markdown,而是将挑战数据(题面、测试、代码模板)同步进 MongoDB 数据库,由 API 层按日期对外提供。相关证据集中在api/目录:

Prisma 数据模型(api/prisma/schema.prisma):

model DailyCodingChallenges { id String @id @default(auto()) @map("_id") @db.ObjectId challengeNumber Int date DateTime title String description String javascript DailyCodingChallengeApiLanguage python DailyCodingChallengeApiLanguage } type DailyCodingChallengeApiLanguage { tests DailyCodingChallengeApiLanguageTests[] challengeFiles DailyCodingChallengeApiLanguageChallengeFiles[] } type DailyCodingChallengeApiLanguageTests { text String testString String } type DailyCodingChallengeApiLanguageChallengeFiles { contents String fileKey String }

该模型的关键设计在于:一道挑战同时携带javascriptpython两套语言的tests(测试断言)和challengeFiles(代码模板),而description中保存的正是如 Challenge 179 那样的题面文本。

公开查询路由(api/src/daily-coding-challenge/routes/daily-coding-challenge.ts)提供 6 个只读 GET 端点:

端点说明
GET /daily-coding-challenge/date/:dateYYYY-MM-DD获取某天挑战
GET /daily-coding-challenge/day/:dayMM-DD(不限定年份)获取
GET /daily-coding-challenge/today获取今日挑战(按美国中部时间计算)
GET /daily-coding-challenge/month/:monthYYYY-MM列出某月挑战摘要
GET /daily-coding-challenge/all列出全部挑战摘要
GET /daily-coding-challenge/newest返回最新挑战的日期

注意这些路由"只用于获取挑战信息,提交仍走主挑战完成路由"(见 api/src/daily-coding-challenge/README.md)。所有接口都会拒绝返回晚于"今日(美国中部时间)"的挑战,避免提前泄题。请求参数的模式校验定义在 api/src/daily-coding-challenge/schemas/daily-coding-challenge.ts,例如date参数要求format: 'date'day参数要求匹配^\d{2}-\d{2}$

时区处理辅助函数(api/src/daily-coding-challenge/utils/helpers.ts)是"每日"机制的幕后功臣:getNowUsCentral()通过date-fns-tzgetTimezoneOffset('America/Chicago', ...)计算当前美国中部时间;getSourceDate()把任意日期映射回 2025-08-11 至 2026-08-10 这一年原始挑战周期内的对应日期,并专门把 2 月 29 日映射到 2 月 28 日(确保非闰年也能取到题)。

前端页面的呈现

客户端侧,每日挑战的入口由一个小组件渲染(client/src/components/daily-coding-challenge/widget.tsx),它根据美国中部时间生成"今日"挑战的链接getMonthDayUsCentral(),并提供"归档"入口。挑战内部页顶部会显示日期面包屑(client/src/templates/Challenges/components/daily-challenge-bread-crumb.tsx),其中使用 client/src/components/daily-coding-challenge/helpers.ts 中的isValidDateOrMonthDayString/formatDisplayDate校验并格式化日期(例如把"02-14"显示为 "February 14")。日期工具同样把02-29规整为02-28,与后端逻辑保持一致。

测试验证:用 Node.js 本地跑通全部用例

无需搭建完整环境,仅凭官方解答即可在本地 Node.js 环境中验证 6 组断言:

function countChange(change) { const totalCents = change.reduce((sum, coin) => sum + coin, 0); const dollars = Math.floor(totalCents / 100); const cents = totalCents % 100; return `$${dollars}.${cents.toString().padStart(2, "0")}`; } const tests = [ [[25, 10, 5, 1], "$0.41"], [[25, 10, 5, 1, 25, 10, 25, 1, 1, 10, 5, 25], "$1.43"], [[100, 25, 100, 1000, 5, 500, 2000, 25], "$37.55"], [[10, 5, 1, 10, 1, 25, 1, 1, 5, 1, 10], "$0.70"], [[1], "$0.01"], [[25, 25, 25, 25], "$1.00"] ]; let passed = 0; for (const [input, expected] of tests) { const actual = countChange(input); const ok = actual === expected; if (ok) passed++; console.log(`${ok ? "PASS" : "FAIL"} countChange([${input}]) => ${actual} (expected ${expected})`); } console.log(`\n${passed}/${tests.length} tests passed`);

运行后 6 组断言应全部 PASS。若某组失败,可重点检查两处:美元部分是否遗漏前导零(如"$0.41"被写成"$.41")、美分部分是否保证两位(如"$1.4"而非"$1.43")。

小结

Challenge 179 "Pocket Change" 用一道贴近生活的题目,把三个高频 JS API 串成一条清晰解题链:reduce做归约求和、Math.floor/%做整数拆分、padStart做格式化补零,同时暗含"金额用整数美分运算"的工程建议。放到 freeCodeCamp 每日编码挑战的语境下,它还展示了从 markdown 题面(--description--/--hints--/--seed--/--solutions--)到 Prisma 数据模型、再到按日期公开的 REST API 与前端日历时区的完整链路,是理解该系列挑战"如何生产、如何下发、如何判题"的一个绝佳切片。

【免费下载链接】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/10 20:03:12

VCU应用层模型开发与量产编译关键技术解析

1. VCU应用层模型在实车量产中的核心价值在汽车电子电气架构快速迭代的当下&#xff0c;VCU&#xff08;Vehicle Control Unit&#xff09;作为整车控制的"大脑"&#xff0c;其开发模式正经历从传统手写代码向模型化设计的范式转移。应用层模型开发的最大优势在于&am…

作者头像 李华
网站建设 2026/9/10 20:01:15

Wireshark抓包实战:从登录报文分析到MD5弱类型绕过

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

作者头像 李华
网站建设 2026/9/10 20:01:04

电源设计从入门到实战:拓扑、选型、布局与调试

电源这东西&#xff0c;被叫成“心脏”一点都不夸张。你想想&#xff0c;人没了心脏啥也干不了&#xff0c;电子电路也一样——CPU、运放、传感器、电机&#xff0c;每一颗芯片都需要一个稳定、干净的供电环境。很多刚入门的朋友总觉得电源就是插个电、接个线的事&#xff0c;可…

作者头像 李华