freeCodeCamp 算法题实战用 JavaScript 实现数组的对称差Find the Symmetric Difference【免费下载链接】freeCodeCampfreeCodeCamp.orgs open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp本文以 freeCodeCamp 课程中 Algorithms 模块的第一道挑战Find the Symmetric Difference为主体从数学定义出发完整给出挑战的测试用例、官方参考答案与逐行原理剖析并提供一套基于Set的替代实现帮助读者掌握集合对称差运算、左结合折叠fold/reduce以及 JavaScript 变长参数处理的实战写法。一、什么是集合的对称差挑战原文位于 挑战文档首先给出数学定义两个集合的对称差symmetric difference记作△或⊕是由属于其中任一个集合、但不属于两者交集的元素组成的集合。以A {1, 2, 3}和B {2, 3, 4}为例1只在 A 中4只在 B 中2、3在两边都有因此A △ B {1, 4}。第二个关键性质是对称差是二元运算一次只能作用于两个操作数。要计算三个集合的对称差A △ B △ C必须逐个完成运算即按左结合顺序逐步折叠。原文给出的例子是已知A {1, 2, 3}、B {2, 3, 4}、C {2, 3}则A △ B △ C (A △ B) △ C {1, 4} △ {2, 3} {1, 2, 3, 4}。这条“逐步折叠”的规则直接决定了代码实现结构不能用任何一步性的整体算法而要把前一个结果与下一个集合继续做对称差直到所有集合处理完——这正是后面reduce出现的原因。二、挑战要求与完整测试用例挑战的指令是编写一个函数接收两个或两个以上的数组作为参数返回它们对称差组成的数组。返回的数组只能包含唯一值不能有重复元素。注意两点约束一是参数个数不定2 or more实现时必须处理变长参数二是即便输入数组内部存在重复值如[1, 2, 3, 3]输出也必须去重。测试断言形式挑战测试套件使用两类断言来自原文--hints--小节assert.sameMembers(actual, expected)两个数组包含相同的元素集合不要求顺序一致assert.equal(actual.length, n)结果数组长度精确等于 n用于排除“元素对但有重复”的实现。这两类断言组合起来意味着顺序不重要但元素集合和唯一性都必须正确。全部测试用例原文档完整继承输入期望输出结果长度sym([1, 2, 3], [5, 2, 1, 4])[3, 4, 5]3sym([1, 2, 3, 3], [5, 2, 1, 4])[3, 4, 5]3sym([1, 2, 3], [5, 2, 1, 4, 5])[3, 4, 5]3sym([1, 2, 5], [2, 3, 5], [3, 4, 5])[1, 4, 5]3sym([1, 1, 2, 5], [2, 2, 3, 5], [3, 4, 5, 5])[1, 4, 5]3sym([3, 3, 3, 2, 5], [2, 1, 5, 7], [3, 4, 6, 6], [1, 2, 3])[2, 3, 4, 6, 7]5sym([3, 3, 3, 2, 5], [2, 1, 5, 7], [3, 4, 6, 6], [1, 2, 3], [5, 3, 9, 8], [1])[1, 2, 4, 5, 6, 7, 8, 9]8对应用例原文的断言写法如下节选两个代表用例其余同理// 两个数组[1,2,3] △ [5,2,1,4] [3,4,5]集合比较无序 assert.sameMembers(sym([1, 2, 3], [5, 2, 1, 4]), [3, 4, 5]); assert.equal(sym([1, 2, 3], [5, 2, 1, 4]).length, 3); // 六个数组的连对称差验证多参数折叠 内部去重 assert.sameMembers( sym( [3, 3, 3, 2, 5], [2, 1, 5, 7], [3, 4, 6, 6], [1, 2, 3], [5, 3, 9, 8], [1] ), [1, 2, 4, 5, 6, 7, 8, 9] ); assert.equal( sym([3, 3, 3, 2, 5], [2, 1, 5, 7], [3, 4, 6, 6], [1, 2, 3], [5, 3, 9, 8], [1]).length, 8 );手工推演最后一个用例可以验证“左结合 逐步 XOR”的规则初始结果{3, 2, 5}与[2, 1, 5, 7]→{1, 2, 3, 7}2、5在两边都有被剔除再与[3, 4, 6, 6]去重后{3, 4, 6}→{1, 2, 4, 6, 7}再与[1, 2, 3]→{3, 4, 6, 7}再与[5, 3, 9, 8]→{3, 4, 5, 6, 7, 8, 9}最后与[1]→{1, 2, 4, 5, 6, 7, 8, 9}共 8 个元素与期望完全一致。起点代码seed原文档给出的待实现骨架是function sym(args) { return args; } sym([1, 2, 3], [5, 2, 1, 4]);即一个直接回显参数的桩函数需要替换为真正支持“两个或更多数组 去重 对称差折叠”的实现。三、官方参考答案逐行解析原文档附带的官方参考实现--solutions--小节function sym() { var arrays [].slice.call(arguments); return arrays.reduce(function (symDiff, arr) { return symDiff.concat(arr).filter(function (val, idx, theArr) { return theArr.indexOf(val) idx (symDiff.indexOf(val) -1 || arr.indexOf(val) -1); }); }); } sym([1, 2, 3], [5, 2, 1, 4]);拆解为四步收集变长参数function sym()不声明形参而是通过[].slice.call(arguments)把arguments类数组转成真正的数组arrays。这是 ES5 时代处理不定参的经典写法现代写法可用剩余参数function sym(...arrays)或Array.from(arguments)替代语义等价。reduce做左结合折叠arrays.reduce(...)的累加器symDiff保存“到目前为止的对称差结果”回调逐个消费每个输入数组arr。这正对应第一部分数学定义里 “A △ B △ C (A △ B) △ C” 的逐步运算规则——第一次迭代时累加器是reduce的默认起始值arrays[0]本身……严格地说此实现没有传初始值首次回调的symDiff是第一个数组、arr是第二个数组恰好就是两两对称差之后每一轮都是“已有结果 △ 下一个数组”与左结合语义一致。concat合并候选元素symDiff.concat(arr)把累积结果和当前数组拼成候选池。注意这个候选池不去重重复值会出现多次。filter一次性完成“去重 取对称差”回调里的两个条件是逻辑与theArr.indexOf(val) idxval在本次扫描中首次出现indexOf返回第一个匹配下标由此过滤掉symDiff与arr内部各自的重复项保证结果唯一(symDiff.indexOf(val) -1 || arr.indexOf(val) -1)val不同时存在于累积结果和当前数组中——即它至少在一边缺席这正是对称差“属于其中之一、但不属于两者”的判定式等价于逻辑异或。两个条件叠加后[1, 2, 3] △ [5, 2, 1, 4]得到[3, 4, 5]且对[1, 2, 3, 3]这类含重复的输入也能输出 3 个元素而非 4 个满足全部测试用例。从源码结构看该实现对每个元素调用多次indexOfO(n) 查找整体是朴素解法作为教学题它把“折叠 判定”写进了一个filter里可读性优先于性能。四、基于 Set 的替代实现用Set可以把“去重”与“对称差判定”表达得更直白同时把每步查找降到 O(1) 级别function sym(...arrays) { return arrays.reduce((acc, arr) { const accSet new Set(acc); // 累积结果自动去重 const arrSet new Set(arr); // 当前数组自动去重 const result new Set(); // 只属于累积结果一侧的元素 for (const v of accSet) { if (!arrSet.has(v)) result.add(v); } // 只属于当前数组一侧的元素 for (const v of arrSet) { if (!accSet.has(v)) result.add(v); } return [...result]; }, []); } sym([1, 2, 3], [5, 2, 1, 4]); // [1, 2, 3]首步空集 △ 第一个数组 sym([1, 2, 5], [2, 3, 5], [3, 4, 5]); // [1, 4, 5] sym([3, 3, 3, 2, 5], [2, 1, 5, 7], [3, 4, 6, 6], [1, 2, 3], [5, 3, 9, 8], [1]) // [1, 2, 4, 5, 6, 7, 8, 9]要点new Set(arr)天然消掉了输入数组内部的重复因此长度断言如length 3不会失手显式传入初始值[]使第一次迭代为空集 △ 第一个数组 第一个数组语义与官方实现一致也避免了reduce不传初始值时首元素被特殊处理的隐含行为两段for...of分别收集“仅左侧”与“仅右侧”的元素就是二元对称差定义的直译便于在代码评审中对照数学定义检查正确性。两个版本都遵循“左结合逐步折叠”的同一套规则因此在原文档列出的全部 7 组用例上结果一致。五、该挑战在 freeCodeCamp 课程结构中的位置这道题不是孤立练习它在仓库课程结构中有明确坐标Algorithms 模块结构 中challengeOrder的第一项即{ id: a3f503de51cf954ede28891d, title: Find the Symmetric Difference }其后依次是Inventory Update、No Repeats Please、Pairwise以及冒泡、选择、插入、快速、归并排序和二分查找共 10 道挑战模块helpCategory标记为JavaScript布局为legacy-challenge-list。从 Coding Interview Prep 超级模块定义 看blocks: [algorithms, data-structures, take-home-projects]即本挑战属于面试准备方向算法模块的开篇题——它考察的正是面试高频基础能力集合运算、变长参数、reduce折叠与去重过滤为后续排序/查找算法题铺垫了函数式处理手法。挑战文档本身遵循 freeCodeCamp 课程 Markdown 的分区约定---frontmatterid、dashedName、challengeType等元数据# --description--、# --instructions--、# --hints--测试用例、# --seed--起点代码、# --solutions--参考答案这一结构在 curriculum/challenges/english/blocks/algorithms/ 目录下的所有挑战文件中保持一致。六、小结对称差A △ B 属于 A 或 B 但不同时属于两者的元素集合多集合运算按左结合逐对折叠实现核心是三件事收集变长参数、用reduce折叠、每轮做“并集后过滤出单侧元素 首次出现判定”官方解法用indexOf(val) idx完成去重、用indexOf -1的或条件完成对称差判定Set版本则更贴近数学定义且查找更快测试侧用sameMembers无序成员相等length精确长度双重断言顺序无关但元素集合与唯一性必须完全正确。掌握这一题后读者应能独立处理任意“多数组折叠 元素唯一性约束”类问题并理解 freeCodeCamp 算法模块中从集合运算到排序查找的知识递进路径。【免费下载链接】freeCodeCampfreeCodeCamp.orgs open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考