news 2026/9/10 18:31:32

freeCodeCamp Daily Coding Challenge 301 “Last Load“ 全解析:用平均值预测洗衣粉剩余天数

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
freeCodeCamp Daily Coding Challenge 301 “Last Load“ 全解析:用平均值预测洗衣粉剩余天数

freeCodeCamp Daily Coding Challenge 301 "Last Load" 全解析:用平均值预测洗衣粉剩余天数

【免费下载链接】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-javascript模块的第 301 道每日编程挑战"Last Load",完整拆解其题目语义、输入输出契约、五个官方测试用例的推导过程,以及官方标准解法的核心思路(历史均值 + 向下取整)。同时结合仓库中的块结构定义、API 路由实现、数据库种子脚本与前端组件,说明这道题如何在 freeCodeCamp 的每日编程挑战体系中从 Markdown 题目文件流转到真实产品页面,帮助你既掌握算法本身,也理解整套基础设施的运行机制。

一、题目速览:问题陈述与函数契约

题目原文位于 6a0dcc730cb92a616f86f0c5.md,属于daily-coding-challenges-javascript块(块结构定义见 daily-coding-challenges-javascript.json)。

题目描述(原文直译):

给定你剩余的洗衣粉勺数(scoops),以及一个数组,记录你在过去每天使用的勺数(usage),返回你还能使用洗衣粉的完整天数

从使用历史中计算你的平均每日用量,并假设未来每天都是这个用量。

函数契约

说明
函数名lastLoadDate(scoops, usage)
参数 1scoops:剩余洗衣粉的总勺数(Number)
参数 2usage:过去若干天每日用量的数组(Number[])
返回值剩余可用洗衣粉的完整天数(Number,向下取整)

这个函数命名看起来略有歧义——lastLoadDate字面含义是"最后一次洗衣的日期",但题目实际要求的是"还能洗多少次完整的衣服"(即剩余天数)。理解这一点是解题的关键:返回值是天数而非日期。

二、官方测试用例逐条推导

题目通过# --hints--段给出 5 个官方断言,源码中每条断言都使用assert.equal精确校验返回值,我们逐条验证解题逻辑的正确性。

用例 1:稳定用量

assert.equal(lastLoadDate(10, [2, 2, 2, 2, 2, 2, 2]), 5);
  • 每日用量:[2, 2, 2, 2, 2, 2, 2],平均每日用量 =14 / 7 = 2
  • 剩余天数 =10 / 2 = 5,向下取整后仍为5

用例 2:含零用量的波动数据

assert.equal(lastLoadDate(16, [2, 3, 0, 3, 4, 2, 1]), 7);
  • 总用量 =2 + 3 + 0 + 3 + 4 + 2 + 1 = 15,平均每日用量 =15 / 7 ≈ 2.1429
  • 剩余天数 =16 / 2.1429 ≈ 7.467,向下取整为7

注意:即使某一天用量为0(没洗衣服),该天依然计入分母usage.length,这保证了平均值能真实反映"摊到每一天"的用量。

用例 3:非整除场景

assert.equal(lastLoadDate(33, [5, 0, 4, 3, 3, 2]), 11);
  • 总用量 =5 + 0 + 4 + 3 + 3 + 2 = 17,平均每日用量 =17 / 6 ≈ 2.8333
  • 剩余天数 =33 / 2.8333 ≈ 11.647,向下取整为11

用例 4:大数值混合波动

assert.equal(lastLoadDate(50, [2, 0, 2, 9, 12, 0, 2]), 12);
  • 总用量 =2 + 0 + 2 + 9 + 12 + 0 + 2 = 27,平均每日用量 =27 / 7 ≈ 3.8571
  • 剩余天数 =50 / 3.8571 ≈ 12.963,向下取整为12

用例 5:高消耗场景下取整的重要性

assert.equal(lastLoadDate(20, [13, 9, 12, 10, 8]), 1);
  • 总用量 =13 + 9 + 12 + 10 + 8 = 52,平均每日用量 =52 / 5 = 10.4
  • 剩余天数 =20 / 10.4 ≈ 1.923若不取整会得到1.923,但题目要求"完整天数",因此向下取整为1

最后一个用例最能体现Math.floor的必要性:1.92 天意味着第 2 天的用量不足,所以只能算 1 个完整天。

三、官方解法:一行核心计算 + 向下取整

题目文件末尾的# --solutions--段给出了官方标准解:

function lastLoadDate(scoops, usage) { const avg = usage.reduce((sum, n) => sum + n, 0) / usage.length; return Math.floor(scoops / avg); }

解法拆解

  1. 求和usage.reduce((sum, n) => sum + n, 0)遍历数组累加所有历史用量,0作为初始值保证空数组安全;
  2. 求均值:除以usage.length得到平均每日用量avg。这一行同时隐含了题目的核心建模——用历史均值代表未来的恒定日耗
  3. 预测天数scoops / avg计算出按当前均值能支撑的天数(可能为小数);
  4. 向下取整Math.floor()只保留"完整"的天数,舍弃不足以支撑一整天的零头,严格符合题目"number offulldays"的要求。

选手可能写出的等价实现

function lastLoadDate(scoops, usage) { const total = 0; for (const n of usage) { total += n; } const avg = total / usage.length; return Math.floor(scoops / avg); }

两种写法在语义上等价,官方解法更依赖reduce,体现函数式风格;无论哪种,avg的计算与Math.floor的取整是得分的关键

四、边界情况与陷阱分析

从测试用例和官方实现中可以提炼出以下容易踩坑的点:

边界/陷阱分析
必须向下取整题目明确要求 "full days"。Math.floor是标准做法;Math.roundMath.ceil都会在用例 3、4、5 上得到错误答案
均值可能不是整数用例 2 中均值 ≈ 2.14,用例 3 中 ≈ 2.83,直接除会产生小数,取整时机要在最后一步
0 值天不能丢弃用例 2、4 都包含0,若先过滤 0 再求平均会得到偏高的均值、偏少的天数
空数组题目未提供空数组用例;若usage为空,avg会得到NaN。官方用例未覆盖,但严谨实现可先做防御(不过按题目语境,历史记录始终存在)
除法结果为 1 以下用例 5 中20 / 10.4 ≈ 1.92,取整后为1,验证了"连 2 天都不够"的语义

另外注意scoops的单位是"勺"(scoop),usage数组元素同为"勺/天",单位一致才能直接相除得到天数。

五、仓库中的挑战工程化:从 Markdown 到产品页

这道题并非孤立存在的习题文件,它在 freeCodeCamp 仓库中属于一套完整的每日编程挑战基础设施,理解这套链路能帮你更好地把握题目在整个平台中的定位。

5.1 块结构与 365 天挑战序列

块元数据 daily-coding-challenges-javascript.json 中:

  • 该块的helpCategoryJavaScriptusesMultifileEditortrue(多文件编辑器),blockLayoutlegacy-challenge-list
  • challengeOrder数组按顺序登记了365 道挑战(Challenge 1: Vowel Balance 至 Challenge 365: Bucket Fill 3),Challenge 301 "Last Load" 的 id6a0dcc730cb92a616f86f0c5与题目文件 frontmatter 中的id一一对应;
  • 365 这个数字与种子脚本的校验值一致(见下文),恰好构成一整年的每日一题。

5.2 种子脚本:题目如何进入数据库

tools/daily-challenges/seed-daily-challenges.ts 负责把挑战写入 MongoDB 的DailyCodingChallenges集合:

  • 通过 GraphQL 从 dev-playground superblock 拉取 JavaScript 与 Python 各 365 道题,数量不匹配即抛错;
  • 起始日期硬编码为2025-08-11T00:00:00.000Z(UTC),并带有一个防误改的 sanity check——一旦起始日期被意外改动,脚本会直接抛出异常,保护已发布的生产数据;
  • 第 i 道题的日期 = 起始日期 +i * 24h,即每天一道,正好铺满一年;
  • 使用replaceOne+upsertbulkWrite实现幂等写入。

5.3 API 路由:按日期/天/月查询挑战

后端 api/src/daily-coding-challenge/routes/daily-coding-challenge.ts 暴露了 6 个公开 GET 端点:

端点说明
/daily-coding-challenge/date/:dateYYYY-MM-DD获取某日挑战,晚于当日(美中时间)返回 404
/daily-coding-challenge/day/:dayMM-DD获取,内部通过getSourceDate映射回原始挑战年份
/daily-coding-challenge/today返回今天(美中时区)的挑战
/daily-coding-challenge/month/:monthYYYY-MM返回整月挑战列表(id、challengeNumber、date、title)
/daily-coding-challenge/all返回全部已发布挑战的摘要列表
/daily-coding-challenge/newest返回最新一条挑战的日期

其中日期解析与"未来不可见"逻辑依赖 helpers.ts:getNowUsCentral基于date-fns-tz计算America/Chicago时区偏移,getUtcMidnight将任意时间归一到 UTC 零点;getSourceDate则把任意年份的MM-DD映射回2025-08-11 ~ 2026-08-10这一年的原始挑战日期(2 月 29 日被映射到 2 月 28 日),从而支持每年循环复用同一批题目。

5.4 客户端组件与数据校验

前端页面通过 widget.tsx 展示入口按钮("今天的每日挑战"与"挑战存档"),跳转到/learn/daily-coding-challenge/{MM-DD}/learn/daily-coding-challenge/archive

数据库返回的数据会先经过 daily-coding-challenge-validator.ts 的 Joi 校验,该文件约束了每个挑战的结构:

{ id: string, challengeNumber: number(integer, min 1), title: string, date: string, description: string, javascript: { tests: [{ text, testString }], challengeFiles: [{ fileKey, contents }] }, python: { tests: [{ text, testString }], challengeFiles: [{ fileKey, contents }] } }

也就是说,"Last Load"这道题在数据库中以javascript+python双语言形式存储,testString正是本文开头那 5 条assert.equal断言,用户提交代码后由平台在浏览器端(或服务端)运行这些断言来判定对错。

六、本地动手:如何验证你的解法

由于挑战断言独立于平台,你完全可以在本地 Node.js 环境中直接验证:

function lastLoadDate(scoops, usage) { const avg = usage.reduce((sum, n) => sum + n, 0) / usage.length; return Math.floor(scoops / avg); } const cases = [ [10, [2, 2, 2, 2, 2, 2, 2], 5], [16, [2, 3, 0, 3, 4, 2, 1], 7], [33, [5, 0, 4, 3, 3, 2], 11], [50, [2, 0, 2, 9, 12, 0, 2], 12], [20, [13, 9, 12, 10, 8], 1] ]; for (const [scoops, usage, expected] of cases) { const actual = lastLoadDate(scoops, usage); console.log( `lastLoadDate(${scoops}, [${usage}]) => ${actual} (expected ${expected})`, actual === expected ? 'PASS' : 'FAIL' ); }

运行后 5 条用例应全部输出PASS。你也可以用这个脚手架继续做变体实验,例如:

  • Math.floor换成Math.ceil/Math.round,观察用例 3、4、5 的断言如何失败,从而加深对"完整天数"语义的理解;
  • 给函数传入空数组,观察avg变为NaN的行为,并思考是否需要防御式编程;
  • 改用for...of循环累加,体会与reduce的等价性。

七、总结

"Last Load"(Challenge 301)是一道典型的"均值预测 + 取整"应用题:用reduce求历史均值、用scoops / avg预测天数、用Math.floor满足"完整天数"的语义约束,官方解法仅三行却覆盖了取整、零值、非整除等全部陷阱。而在 freeCodeCamp 仓库中,这道题以 Markdown 题目文件为源,经由challengeOrder编号、种子脚本入库、REST API 暴露、Joi 校验与前端组件渲染,最终成为每日编程挑战页面上的可提交题目——读懂这一整条链路,既是一次算法练习,也是一次对开源平台内容管线的微观观察。

延伸阅读:同一模块的其他题目可继续翻阅 curriculum/challenges/english/blocks/daily-coding-challenges-javascript/ 目录;API 层设计见 api/src/daily-coding-challenge/;种子脚本与客户端组件分别位于 tools/daily-challenges/seed-daily-challenges.ts 与 client/src/components/daily-coding-challenge/。

【免费下载链接】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 18:28:30

Caddy vs Nginx:极简配置与自动HTTPS,谁更适合作业?

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

作者头像 李华