freeCodeCamp 每日编程挑战实战解析:Challenge 290 "Pizza Party" 的向上取整与聚合算法
【免费下载链接】freeCodeCampfreeCodeCamp.org's open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp
本篇技术指南以 freeCodeCamp 仓库中的 Challenge 290: Pizza Party(每日编程挑战系列之一)为核心,从题目规则、数学建模、逐行代码实现到测试用例验证,完整剖析这道数组聚合类算法题。读者读完将掌握Math.ceil向上取整、Math.max下限约束、Array.reduce聚合等 JavaScript 核心技巧,并了解该挑战在 freeCodeCamp 每日编程挑战体系(challengeType 28)中的真实运行机制。
一、题目总览:为披萨派对计算订购数量
挑战文件位于 curriculum/challenges/english/blocks/daily-coding-challenges-javascript/69f8c998d78ad3171a0713bd.md,归属于daily-coding-challenges-javascript挑战块。题目场景非常贴近生活:
给定一个数组,表示今天每个人的工作时长(小时),请返回为披萨派对需要订购的披萨数量。
要完成这道题,必须严格遵循以下四条业务规则:
- 切片换算:将每个人的工作时长除以 3,得到其应得的披萨切片数;
- 禁止半片:不能吃半片披萨,因此每个人的切片数需向上取整到最近的整数;
- 最低保障:每个人至少分到 2 片;
- 整披萨订购:每份披萨含 8 片,总切片数换算成披萨后需向上取整到最近的整数个披萨。
这是一个典型的"分步计算 → 逐项聚合 → 最终取整"的数组处理问题,考察的是对reduce、Math.ceil、Math.max的组合运用能力。
二、从题目到数学模型
在动手写代码之前,先把文字规则翻译成数学表达式。
设输入数组为hoursWorked(每人工作时长的数组),那么:
第 1 步:单人的切片数
slices_i = max( ceil(hours_i / 3), 2 )ceil(hours_i / 3):实现"除以 3 后向上取整",对应规则 1 和规则 2;max(..., 2):实现"每人至少 2 片"的下限约束,对应规则 3。
第 2 步:总切片数
totalSlices = sum(slices_i) // 对每个人累加第 3 步:最终披萨数
pizzas = ceil(totalSlices / 8)- 每片披萨 8 片、向上取整,对应规则 4。
值得注意的是,Math.max(Math.ceil(hours / 3), 2)这一表达式把"取整"和"下限"两个逻辑合并成了一步:先算出按工时换算的切片数并取整,再与 2 比较取较大值。这样即使某人的工时小于 6 小时(ceil(hours/3) < 2),也能保证其至少获得 2 片。
三、种子代码与解决方案逐行拆解
3.1 初始种子代码
挑战为学习者提供了如下函数骨架(位于原文档的# --seed--部分):
function getPizzasToOrder(hoursWorked) { return hoursWorked; }学习者需要把return hoursWorked替换为真正的计算逻辑,使函数接收每人工作时长数组、返回订购披萨数。
3.2 官方解决方案
function getPizzasToOrder(hoursWorked) { const totalSlices = hoursWorked.reduce((sum, hours) => { return sum + Math.max(Math.ceil(hours / 3), 2); }, 0); return Math.ceil(totalSlices / 8); }逐行解读:
| 代码片段 | 作用 |
|---|---|
hoursWorked.reduce((sum, hours) => ..., 0) | 遍历数组,以0为初始值累加每个人的切片数 |
Math.ceil(hours / 3) | 每小时 3 片换算并向上取整(规则 1、2) |
Math.max(..., 2) | 与 2 比较,保证每人最少 2 片(规则 3) |
Math.ceil(totalSlices / 8) | 每披萨 8 片,总切片换算后向上取整(规则 4) |
整个方案只用一次reduce就完成了所有人的切片数聚合,再通过一次Math.ceil收尾,时间复杂度和空间复杂度均为O(n)(仅需遍历一次数组、无额外存储),是这道题在性能上的最优解。
四、测试用例验证:理解边界与陷阱
原文档的# --hints--部分给出了 5 个断言用例,每个都用assert.equal校验函数输出。下面逐一演算,帮助理解规则之间的交互:
用例 1:getPizzasToOrder([8, 8, 8])→2
每人 8 小时:ceil(8/3) = 3,均大于 2,故每人 3 片,共 9 片;ceil(9/8) = 2。✅
用例 2:getPizzasToOrder([10, 9, 8, 2, 2, 6, 10])→3
- 10 小时 →
ceil(10/3) = 4 - 9 小时 →
ceil(9/3) = 3 - 8 小时 →
ceil(8/3) = 3 - 2 小时 →
ceil(2/3) = 1,但max(1, 2) = 2 - 2 小时 → 同上,2 片
- 6 小时 →
ceil(6/3) = 2 - 10 小时 → 4 片
合计4+3+3+2+2+2+4 = 20片;ceil(20/8) = 3。✅ 这个用例同时覆盖了"最低 2 片保障"和"整披萨取整"两条规则。
用例 3:getPizzasToOrder([1, 2, 3, 4, 5])→2
- 1 小时 →
max(ceil(1/3), 2) = 2 - 2 小时 →
max(1, 2) = 2 - 3 小时 →
max(1, 2) = 2 - 4 小时 →
max(ceil(4/3), 2) = max(2, 2) = 2 - 5 小时 →
max(2, 2) = 2
合计 10 片;ceil(10/8) = 2。✅ 该用例集中考验"工时不足 3 小时也要 2 片"的边界逻辑。
用例 4:getPizzasToOrder([8, 8, 8, 8, 8, 8, 8, 8])→3
8 人各 3 片,共 24 片;ceil(24/8) = 3。✅ 恰好整批 3 个披萨,验证无余数时的取整行为。
用例 5:getPizzasToOrder([9, 9, 6])→1
- 9 小时 →
ceil(9/3) = 3(两次) - 6 小时 →
ceil(6/3) = 2
合计 8 片;ceil(8/8) = 1。✅ 恰好 1 个披萨,再次验证"整除"场景。
用例 6:getPizzasToOrder([10, 12, 16, 9, 8, 11, 15, 8, 0])→5
- 10 → 4、12 → 4、16 →
ceil(16/3)=6、9 → 3、8 → 3、11 →ceil(11/3)=4、15 → 5、8 → 3、0 →max(0, 2) = 2
合计4+4+6+3+3+4+5+3+2 = 34片;ceil(34/8) = 5。✅ 该用例包含 0 工时(考验最低切片保障)和较大数值的聚合。
从这 6 组用例可以看出测试设计覆盖了:一般换算、下限约束、整除与不整除、0 工时等典型边界情况。
五、挑战在 freeCodeCamp 体系中的定位
5.1 挑战块与元数据
本挑战的 frontmatter 声明了challengeType: 28与dashedName: challenge-290,并在 curriculum/structure/blocks/daily-coding-challenges-javascript.json 中注册为"Challenge 290: Pizza Party"(id 为69f8c998d78ad3171a0713bd),前后分别是 Challenge 289 "Sum of Differences" 与 Challenge 291 "FizzBuzz Count"。
从该块配置可看出其工程特征:
"isUpcomingChange": true:属于进行中的课程变更;"usesMultifileEditor": true:在挑战编辑界面使用多文件编辑器;"helpCategory": "JavaScript":归入 JavaScript 帮助分类;"blockLayout": "legacy-challenge-list":采用传统挑战列表布局。
5.2 每日挑战的 API 支撑
此类挑战的数据由 API 侧的 daily-coding-challenge 模块对外提供。路由实现位于 api/src/daily-coding-challenge/routes/daily-coding-challenge.ts,共注册了 6 个公开 GET 端点:
| 端点 | 说明 |
|---|---|
/daily-coding-challenge/date/:date | 按YYYY-MM-DD精确日期获取某天挑战 |
/daily-coding-challenge/day/:day | 按MM-DD获取对应日期的挑战(2 月 29 日映射到 2 月 28 日) |
/daily-coding-challenge/today | 获取美国中部时区(America/Chicago)当天的挑战 |
/daily-coding-challenge/month/:month | 按YYYY-MM获取整月挑战列表 |
/daily-coding-challenge/all | 获取全部挑战列表(仅返回已发布日期) |
/daily-coding-challenge/newest | 获取最新一期挑战的日期 |
请求/响应结构由 TypeBox schema 定义在 api/src/daily-coding-challenge/schemas/daily-coding-challenge.ts:单个挑战响应包含id、date、challengeNumber、title、description以及javascript/python两套语言字段(各含tests与challengeFiles)。Pizza Party 这类题目的测试断言正是在此类结构中以testString形式交付给前端执行的。
5.3 日期相关的实现细节
源码中有几个值得注意的日期处理细节(见 api/src/daily-coding-challenge/utils/helpers.ts 与路由文件):
- "今天"以美国中部时间为准,由
getNowUsCentral()计算,再经getUtcMidnight归一化为 UTC 零点用于数据库查询; getSourceDate会把MM-DD映射回具体的 UTC 日期,并处理闰年 2 月 29 日这类边界;- 所有查询都会过滤
date <= today US Central的挑战,避免返回未发布的未来题目(路由注释明确指出最后一个挑战日期为 2026 年 8 月 10 日之后将不再返回数据)。
六、举一反三:从本题延伸的实战要点
6.1 取整三件套的选择
Math.ceil(x):向上取整,适合"必须准备足量"的场景(本题的披萨、物资采购、服务器容量规划);Math.floor(x):向下取整,适合"最多能装多少"的场景;Math.round(x):四舍五入,适合统计平均类场景。
6.2 用Math.max施加下限约束
Math.max(value, min)是给计算结果设置下限的惯用写法,等价于value < min ? min : value。同理,Math.min(value, max)可以施加上限。本题的"每人至少 2 片"正是这种模式的典型应用。
6.3reduce的聚合思维
当需要把数组中的每一项折算成一个数值时,reduce(callback, initialValue)是最直接的工具。本题把"每人切片数"在回调内即时计算并累加,避免先map再sum的两趟遍历,代码更紧凑。若希望可读性优先,也可拆成两步:
function getPizzasToOrder(hoursWorked) { const slicesPerPerson = hoursWorked.map(hours => Math.max(Math.ceil(hours / 3), 2) ); const totalSlices = slicesPerPerson.reduce((sum, s) => sum + s, 0); return Math.ceil(totalSlices / 8); }两种写法结果一致,前者更高效,后者更利于调试中间值。
6.4 可能的陷阱
- 忘记最低切片保障:若直接
sum += Math.ceil(hours / 3),则[1, 2]这类用例会得到ceil(1/3)+ceil(2/3) = 2片、1 个披萨,而正确答案要求每人 2 片,即 4 片、1 个披萨——对于本例恰好一致,但在更大的输入下会显著低估订购量; - 先取整再求和 vs 先求和再取整:必须"每人分别向上取整后再累加",而不是"总工时除以 3 后一次性取整",否则会系统性少算切片;
- 忽略 0 工时成员:
ceil(0/3) = 0,若不做max(..., 2)约束,0 工时者将得不到任何切片。
七、总结
Challenge 290: Pizza Party 是一道短小精悍的数组聚合题:通过"工时 ÷ 3 向上取整、每人至少 2 片、每 8 片 1 个披萨向上取整"三层规则,串起了Math.ceil、Math.max、Array.prototype.reduce三个高频 API,并用 6 组覆盖边界条件的断言固化了正确行为。学习者既可以把它当作日常算法练习,也可以顺着本仓库的 challenge 文档、挑战块配置 与 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),仅供参考