freeCodeCamp 课程挑战实战:Return Largest Numbers in Arrays 的题目解析、官方解法与测试机制
【免费下载链接】freeCodeCampfreeCodeCamp.org's open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp
本文以 freeCodeCamp 课程(curriculum)中的 JavaScript 算法练习 "Return Largest Numbers in Arrays"(返回每个子数组的最大值)为核心,完整讲解题目要求、全部 4 条断言测试、种子代码与官方解法的实现原理,并结合仓库中的课程结构文件与挑战类型配置,说明该挑战在 freeCodeCamp 课程体系中的定位与执行方式。读完后你不仅能独立写出多种解法,还能理解课程 Markdown 挑战文件是如何被校验、如何驱动前端编辑器与测试运行的。
题目描述与输入输出约定
挑战文件的正文描述(description)原文为:
Return an array consisting of the largest number from each provided sub-array. For simplicity, the provided array will contain exactly 4 sub-arrays.
即:给定一个二维数组(外层数组包含若干子数组),返回一个新数组,新数组的第 i 个元素是原数组第 i 个子数组中的最大值。为降低难度,题目约定输入数组恰好包含 4 个子数组,且每个子数组恰好含 4 个数字。描述中还给出了一条学习提示:可以用简单的 for 循环遍历数组,并通过数组语法arr[i]访问每个成员——这也是本题面向的知识点(循环 + 数组下标访问 + 比较操作)。
输入输出约定如下:
| 项 | 约定 |
|---|---|
入参arr | 二维数组,长度固定为 4,每个子数组为 4 个数字 |
| 出参 | 一维数字数组,长度 4,result[i]为arr[i]中最大的数 |
| 函数名 | largestOfFour |
| 边界情况 | 子数组元素可能全部为负数,最大值仍是其中最大的负数 |
全部断言测试(hints)逐条解读
题目文件中的# --hints--区块定义了 4 条断言,它们是判定解法是否通过的唯一标准,完整继承如下。
测试 1:返回值必须是数组
largestOfFour([[4, 5, 1, 3], [13, 27, 18, 26], [32, 35, 37, 39], [1000, 1001, 857, 1]])应当返回一个数组:
assert.isArray( largestOfFour([ [4, 5, 1, 3], [13, 27, 18, 26], [32, 35, 37, 39], [1000, 1001, 857, 1] ]) );这条测试只检查类型,用于拦住"返回单个最大值"或"返回对象"之类的实现。
测试 2:常规正数输入
largestOfFour([[13, 27, 18, 26], [4, 5, 1, 3], [32, 35, 37, 39], [1000, 1001, 857, 1]])应返回[27, 5, 39, 1001]:
assert.deepEqual( largestOfFour([ [13, 27, 18, 26], [4, 5, 1, 3], [32, 35, 37, 39], [1000, 1001, 857, 1] ]), [27, 5, 39, 1001] );测试 3:最大值可能位于子数组任意位置
largestOfFour([[4, 9, 1, 3], [13, 35, 18, 26], [32, 35, 97, 39], [1000000, 1001, 857, 1]])应返回[9, 35, 97, 1000000]:
assert.deepEqual( largestOfFour([ [4, 9, 1, 3], [13, 35, 18, 26], [32, 35, 97, 39], [1000000, 1001, 857, 1] ]), [9, 35, 97, 1000000] );最大值分别出现在各子数组的第 2、2、3、1 个位置,验证实现不能只比较固定下标。
测试 4:全负数子数组
largestOfFour([[17, 23, 25, 12], [25, 7, 34, 48], [4, -10, 18, 21], [-72, -3, -17, -10]])应返回[25, 48, 21, -3]:
assert.deepEqual( largestOfFour([ [17, 23, 25, 12], [25, 7, 34, 48], [4, -10, 18, 21], [-72, -3, -17, -10] ]), [25, 48, 21, -3] );最后一组[-72, -3, -17, -10]的最大值是-3。这条测试专门拦截"初始最大值写成 0"这类错误——如果求最大值时把比较基线设为0,全负数子数组会错误地返回0。
种子代码(seed)
# --seed--区块给出了学员在代码编辑器中看到的初始代码,函数体只是原样返回入参,任务就是把return arr;替换为真正的求解逻辑:
function largestOfFour(arr) { return arr; } largestOfFour([ [4, 5, 1, 3], [13, 27, 18, 26], [32, 35, 37, 39], [1000, 1001, 857, 1] ]);官方解法解析:map + Math.max.apply
# --solutions--区块给出的官方参考答案只有核心一行:
function largestOfFour(arr) { return arr.map(subArr => Math.max.apply(null, subArr)); } largestOfFour([ [4, 5, 1, 3], [13, 27, 18, 26], [32, 35, 37, 39], [1000, 1001, 857, 1] ]);实现要点拆解:
arr.map(subArr => ...):对每个子数组执行一次"求最大值"操作,map自动把每次的返回值按顺序收集成新数组,天然满足"返回一个数组、顺序与原数组一致"的要求。Math.max.apply(null, subArr):Math.max的签名是变参函数Math.max(a, b, c, ...),而subArr是一个数组。apply的作用正是"把一个数组展开为函数的实参列表",因此Math.max.apply(null, subArr)等价于依次把subArr的每个元素传给Math.max,第一个参数this绑定为null(Math.max不依赖this)。- 该写法与展开运算符写法
Math.max(...subArr)语义完全等价;官方解法采用apply是兼容更早 JavaScript 运行环境的经典习惯。
备选解法:从 for 循环到 reduce
题目描述提示使用 for 循环与arr[i]访问,下面给出与该提示一致的朴素解法,以及两种函数式变体,均可通过上述 4 条断言。
解法一:双层 for 循环(对应题目提示的写法)
function largestOfFour(arr) { const result = []; for (let i = 0; i < arr.length; i++) { let max = arr[i][0]; // 用子数组首个元素作基线,避免 0 对全负数失效 for (let j = 1; j < arr[i].length; j++) { if (arr[i][j] > max) { max = arr[i][j]; } } result.push(max); } return result; }注意比较基线取arr[i][0]而非0,这是通过测试 4(全负数子数组)的关键。
解法二:map + reduce
function largestOfFour(arr) { return arr.map(subArr => subArr.reduce((max, num) => (num > max ? num : max)) ); }reduce不传初始值时,累加器初始为数组第一个元素,同样天然规避了全负数场景的基线问题。
解法三:map + sort(不推荐但可理解)
function largestOfFour(arr) { return arr.map(subArr => Math.max(...subArr) // 或 subArr.slice().sort((a, b) => b - a).pop() ); }sort默认按字符串字典序比较数字,必须显式提供(a, b) => b - a数值比较器;且sort会原地修改数组,需要先slice()拷贝,避免污染入参。相比之下Math.max方案既简洁又无副作用。
该挑战在 freeCodeCamp 课程仓库中的组织方式
以下结合仓库文件说明这道题是如何被课程系统装载与执行的,帮助理解挑战文件的每个字段。
挑战文件的元数据(frontmatter)
challenge 文件 的 YAML frontmatter 为:
--- id: a789b3483989747d63b0e427 title: Return Largest Numbers in Arrays challengeType: 1 forumTopicId: 16042 dashedName: return-largest-numbers-in-arrays ---其中challengeType: 1的含义可在共享配置 challenge-types.ts 中确认:常量js = 1,即本题属于 JavaScript 挑战。同一文件中还有两个与本类挑战的执行方式直接相关的映射表:
- viewTypes 中
[js]: 'classic',说明该挑战在前端渲染时走 classic 挑战视图,对应模板目录 classic; - submitTypes 中
[js]: 'tests',说明完成提交走tests路径——即本文前面列出的断言会在沙箱环境中对用户代码执行,全部通过才算完成。
而forumTopicId: 16042与dashedName: return-largest-numbers-in-arrays则分别用于论坛主题关联和 URL 友好命名。
在 Block 中的位置
课程 block 的结构定义在 basic-algorithm-scripting.json 中。该文件声明了dashedName: basic-algorithm-scripting、helpCategory: JavaScript以及 16 道挑战的challengeOrder。本题(id: a789b3483989747d63b0e427,标题 "Return Largest Numbers in Arrays")在challengeOrder中排在第 5 位(第 22-25 行),位于 "Factorialize a Number" 之后、"Confirm the Ending" 之前;文件末尾的blockLayout: legacy-challenge-list表明该 block 使用传统的挑战列表布局展示。
挑战文件的结构约束
课程仓库用 Joi 对挑战 Markdown 做结构校验,核心 schema 位于 challenge-schema.js,并配有 快照测试 固化 schema 形态。本题文件采用的分区标记——# --description--、# --hints--、# --seed--(含## --seed-contents--)、# --solutions--——正是课程解析器识别的标准区块:description 渲染为题目区,hints 渲染为测试断言,seed-contents 注入代码编辑器,solutions 仅供答案展示使用。从源码结构看,这些区块名与 schema/解析层一一对应,这也是为什么本文件中每个区块标题必须严格使用# --xxx--形式。
小结
- 本题的契约:输入 4 个各含 4 个数字的子数组,返回每个子数组最大值组成的数组;4 条断言分别覆盖类型检查、常规输入、最大值位置变化、全负数子数组三类场景。
- 官方解法一行核心:
arr.map(subArr => Math.max.apply(null, subArr)),其中apply负责把子数组展开为Math.max的变参实参;map + reduce、双层for循环是等价的替代写法。 - 在仓库层面,该挑战由 挑战文件、block 结构文件、挑战类型配置 与 schema 校验 共同承载:
challengeType: 1决定 classic 视图与tests提交方式,断言通过后即判定挑战完成。
【免费下载链接】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),仅供参考