☰
freeCodeCamp 每日编程挑战 360:Spoken Duration——把秒数翻译成口语化英语时长
2026/10/16 10:25:38 网站建设 项目流程

freeCodeCamp 每日编程挑战 360:Spoken Duration——把秒数翻译成口语化英语时长

【免费下载链接】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 Challenges)系列中的 Challenge 360 "Spoken Duration" 为核心,完整讲解这道题的原始需求、全部测试断言与参考解法:如何把一个纯数字的秒数,按小时/分钟/秒拆解后组合成符合英语口语习惯的时长表达(如"1 hour, 2 minutes and 3 seconds"),并结合同仓库中挑战类型定义与 block 配置文件,说明这道题在 freeCodeCamp 课程体系中的实际落地机制。读完后你可以独立完成这类"数值拆解 + 单复数 + 英文连接词"的字符串组装问题,并理解 freeCodeCamp 挑战文件的 frontmatter、hints、seed 与 solutions 各段落的结构约定。

题目背景:每日编程挑战中的 Challenge 360

该题定义在 6a26df3e2988bcdded204896.md,属于daily-coding-challenges-javascript板块(JavaScript 版每日挑战)。在 block 结构文件 daily-coding-challenges-javascript.json 中可以看到它的登记条目:

{ "id": "6a26df3e2988bcdded204896", "title": "Challenge 360: Spoken Duration" }

它位于 Challenge 358 "Emoji Translator" 与 Challenge 361 "Spoken Time" 之间(后者是它的"姊妹题",处理的是带 AM/PM 的钟表时间)。该 block 的顶部配置还声明了若干运行特性,直接影响做题体验:

{ "isUpcomingChange": true, "dashedName": "daily-coding-challenges-javascript", "usesMultifileEditor": true, "helpCategory": "JavaScript", "disableLoopProtectTests": true, "blockLayout": "legacy-challenge-list", ... }

其中disableLoopProtectTests: true可以理解为关闭了防死循环保护的测试包装,意味着解法中如果出现无限循环不会被特殊熔断逻辑提前截断;usesMultifileEditor: true表示该系列题使用多文件编辑器界面。

挑战类型 28 意味着什么

题目 frontmatter 中的challengeType: 28是理解运行方式的关键。在 challenge-types.ts 中,类型常量的定义确认了 28 即dailyChallengeJs:

const dailyChallengeJs = 28; const dailyChallengePy = 29;

同文件还给出了这类挑战的两条行为映射:

// determine the component view for each challenge export const viewTypes = { ... [dailyChallengeJs]: 'classic', ... }; // determine the type of submit function to use on challenge completion export const submitTypes = { ... [dailyChallengeJs]: 'tests', ... };

也就是说,这道题在前端以classic(经典代码编辑器)视图呈现,提交时走tests提交流程——把用户代码与内置断言一起执行。这也解释了为什么题目文件中的每条提示都是一个assert.equal(...)断言:它们就是判题依据本身。该文件还提供了getIsDailyCodingChallenge、getDailyCodingChallengeLanguage等辅助函数,用于在站点层面区分每日挑战并映射语言(dailyChallengeJs→'javascript')。

题目要求(完整继承)

原题给出的完整需求如下:

Given a number of seconds, return the duration in spoken English.

  • Break the duration into hours, minutes, and seconds.
  • Skip any zero values.
  • Use singular or plural as appropriate ("1 hour","2 hours").
  • If present, join the last two units with"and", and the second and third to last units with a comma ("1 hour, 2 minutes and 3 seconds").

逐条翻译为可验证的约束:

  1. 拆解:输入秒数必须拆成小时、分钟、秒三个单位(1 小时 = 3600 秒,1 分钟 = 60 秒)。
  2. 跳过零值:某个单位的计数为 0 时不输出该单位,而不是输出"0 minutes"之类。
  3. 单复数:计数为 1 时用单数(hour/minute/second),否则用复数(hours/minutes/seconds)。
  4. 英文连接词:当存在多个单位时,最后两个单位之间用" and "连接;倒数第三与倒数第二单位之间用逗号(", ")分隔。即三单位时形如"A hours, B minutes and C seconds",两单位时形如"A hours and B seconds"。

官方断言用例

题目--hints--段落给出了 7 条官方断言,覆盖了全部关键分支(含三单位、两单位、单单位、跨小时跳过分钟、单数形式):

输入期望输出考察点
3723"1 hour, 2 minutes and 3 seconds"三单位全齐 + 小时取单数
7295"2 hours, 1 minute and 35 seconds"三单位 + 分钟取单数
8521"2 hours, 22 minutes and 1 second"三单位 + 秒取单数
435"7 minutes and 15 seconds"小时为 0 被跳过
14455"4 hours and 55 seconds"分钟为 0 被跳过
72000"20 hours"仅剩单单位,无连接词
1"1 second"最小输入 + 单数

以文档原样保留的断言形式:

assert.equal(getSpokenDuration(3723), "1 hour, 2 minutes and 3 seconds"); assert.equal(getSpokenDuration(7295), "2 hours, 1 minute and 35 seconds"); assert.equal(getSpokenDuration(8521), "2 hours, 22 minutes and 1 second"); assert.equal(getSpokenDuration(435), "7 minutes and 15 seconds"); assert.equal(getSpokenDuration(14455), "4 hours and 55 seconds"); assert.equal(getSpokenDuration(72000), "20 hours"); assert.equal(getSpokenDuration(1), "1 second");

初始代码(seed)

做题时编辑器中给出的骨架(--seed-contents--段落):

function getSpokenDuration(seconds) { return seconds; }

初始实现只是原样返回入参,需要完全改写。注意入参名为seconds,与输出单位seconds同名,改写时用局部变量名(如secs)避免混淆。

参考解法逐行解析

--solutions--段落给出的官方参考解:

function getSpokenDuration(seconds) { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const secs = seconds % 60; const parts = []; if (hours) parts.push(`${hours} ${hours === 1 ? "hour" : "hours"}`); if (minutes) parts.push(`${minutes} ${minutes === 1 ? "minute" : "minutes"}`); if (secs) parts.push(`${secs} ${secs === 1 ? "second" : "seconds"}`); if (parts.length === 1) return parts[0]; if (parts.length === 2) return `${parts[0]} and ${parts[1]}`; return `${parts[0]}, ${parts[1]} and ${parts[2]}`; }

可以拆成三个阶段理解:

阶段一:借位拆解。

  • hours = Math.floor(seconds / 3600):整除得到小时数;
  • minutes = Math.floor((seconds % 3600) / 60):先取余去掉整小时,再整除 60 得到分钟数——注意必须用seconds % 3600而不是seconds / 3600 % 60,前者直接隔离出"小时内"的秒数;
  • secs = seconds % 60:最终余数即零头秒。

三个变量相加恒等于输入秒数(hours * 3600 + minutes * 60 + secs === seconds),这是后续手工验证用例的基础。

阶段二:按需收集非零片段。parts数组只推入非零单位,每条推入的字符串内联完成了单复数判断(hours === 1 ? "hour" : "hours"三处同理)。if (hours)/if (minutes)/if (secs)的 truthy 判断天然实现了"跳过零值"——0 是 falsy,对应单位不会进入parts。

阶段三:按长度拼接连接词。拼接策略完全由parts.length决定,与题目第 4 条约束一一对应:

  • length === 1:直接返回唯一片段,没有任何连接词(对应"20 hours"、"1 second");
  • length === 2:A and B(对应"4 hours and 55 seconds"、"7 minutes and 15 seconds");
  • length === 3:A, B and C(对应前三个三单位用例)。

用例手工验证

用阶段一的恒等式逐条核对官方断言:

输入hours / minutes / secsparts 内容拼接分支结果
37231 / 2 / 3["1 hour", "2 minutes", "3 seconds"]3 段"1 hour, 2 minutes and 3 seconds"✓
72952 / 1 / 35["2 hours", "1 minute", "35 seconds"]3 段"2 hours, 1 minute and 35 seconds"✓
85212 / 22 / 1["2 hours", "22 minutes", "1 second"]3 段"2 hours, 22 minutes and 1 second"✓
4350 / 7 / 15["7 minutes", "15 seconds"]2 段"7 minutes and 15 seconds"✓
144554 / 0 / 55["4 hours", "55 seconds"]2 段"4 hours and 55 seconds"✓
7200020 / 0 / 0["20 hours"]1 段"20 hours"✓
10 / 0 / 1["1 second"]1 段"1 second"✓

7 条官方断言全部命中。从源码结构看,解法没有对输入0做防御(此时parts为空、parts.length走最后的 3 段分支会拼出undefined),但官方断言集未覆盖该输入,做题时无需处理;若在自己的项目中复用此逻辑,建议显式约定0的返回值。

可迁移的写法要点

  • "先收集、后拼接"是处理不定长枚举输出的稳妥模式:把每个候选单位作为独立片段推入数组,最后只按数组长度选择连接模板,避免了在拆解阶段就预测"这是几个单位"的分支混乱;
  • truthy 过滤 + 模板字符串内联三元让"跳过零值"与"单复数"两条规则在同一个表达式里完成,无需额外状态;
  • 单复数判断必须发生在片段构造时,因为拼接阶段拿到的已是成品字符串,无法再追溯数量。

这道题在 freeCodeCamp 课程系统中的位置

结合仓库内的实现证据,可以确认该题的完整落地链路:

  1. 内容层:题目正文以 freeCodeCamp 的 challenge Markdown 格式书写,--description--(题目)、--hints--(断言提示)、--seed--/--seed-contents--(初始代码)、--solutions--(参考解)各段落语义固定;frontmatter 中的id、title、dashedName(challenge-360)用于站点路由与锚点。
  2. 结构层:daily-coding-challenges-javascript.json 的challengeOrder数组按 id 注册题目顺序,决定 360 在挑战列表中的位置。
  3. 运行层:challengeType: 28经 challenge-types.ts 映射为classic视图与tests提交类型,用户提交后代码与--hints--中的断言一同执行判定。

该系列中编号相邻的 Challenge 361: Spoken Time 处理的是 12 小时制钟面时间,与本題的"时长口语化"形成对照,适合作为延伸练习。

小结

Challenge 360 用一个短小的函数题集中考察了四件事:整除取余的时间拆解、零值过滤、英文单复数分支、以及不定长元素的连接词拼装。官方参考解的核心模式——拆解为hours/minutes/secs→ truthy 过滤进parts数组 → 按parts.length三档拼接——可以直接迁移到任何"把结构化数值渲染成口语/文案"的场景。题目原文、全部断言与参考解均可在 6a26df3e2988bcdded204896.md 中核对。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询