)
文档/教程前端【免费下载链接】en.javascript.infoModern JavaScript Tutorial项目地址https://gitcode.com/gh_mirrors/en/en.javascript.info点击查看免费下载本文围绕仓库 en.javascript.info 中 Map and Set 章节的经典练习Filter anagrams展开完整还原题目要求、官方给出的两种解法Map版本与普通对象版本并结合仓库内的参考实现与单元测试深入剖析字母排序归一化 键覆盖去重这一通用算法思路。读完本文你将掌握如何利用Map.set(key, value)的同键覆盖语义完成按归一化键分组去重并能在 LeetCode 变位词分组类题目中直接复用该思路。1. 题目要求过滤变位词该练习位于 Map and Set 章节完整题目见 02-filter-anagrams/task.md。变位词Anagram的定义由相同数量的相同字母组成、但排列顺序不同的单词。例如nap - pan ear - are - era cheaters - hectares - teachers题目要求实现一个函数aclean(arr)返回一个已去除变位词的数组从每一组变位词中只保留一个单词保留哪一个不限。给定示例输入let arr [nap, teachers, cheaters, PAN, ear, era, hectares]; alert( aclean(arr) ); // nap,teachers,ear 或 PAN,cheaters,era从输出可以看出两点关键约束三组变位词各保留一个单词因此结果长度为 3大小写不敏感PAN与nap被视为同一组变位词题目示例中明确允许保留其中任意一个。2. 核心思路字母排序得到归一化键官方解法见 02-filter-anagrams/solution.md给出的核心洞察非常简洁把每个单词拆成字母、排序后再拼回同一组变位词会得到完全相同的字符串。nap, pan - anp ear, era, are - aer cheaters, hectares, teachers - aceehrst ...于是判断两个单词是否为变位词就被转化为比较两个字符串是否相等复杂度从逐字母比对降为一次排序后比较。以排序后的字符串作为Map的键天然形成分组第一次遇到某个归一化键时map.set(sorted, word)把单词存入之后再遇到同组单词时set会用新值覆盖旧值键仍然只有一个最终map中每个键恰好对应一组变位词map.values()即为去重后的结果。3. 解法一基于Map的官方实现仓库中的参考实现位于 _js.view/solution.js核心代码如下function aclean(arr) { let map new Map(); for (let word of arr) { // split the word by letters, sort them and join back let sorted word.toLowerCase().split().sort().join(); map.set(sorted, word); } return Array.from(map.values()); } let arr [nap, teachers, cheaters, PAN, ear, era, hectares]; alert( aclean(arr) );3.1 归一化链路逐段拆解关键的排序归一化是第 6 行的一次链式调用官方解答将其拆开以便理解let sorted word // PAN .toLowerCase() // pan .split() // [p,a,n] .sort() // [a,n,p] .join(); // anp各环节作用与注意事项步骤方法作用注意点1toLowerCase()统一大小写使PAN与nap归入同一键题目要求大小写不敏感此步必不可少2split()把单词拆成单字符数组对多字节字符如 emoji、组合字符不适用见下文边界讨论3sort()字母按 Unicode 码点升序排列无比较函数时按码点排序英文小写字母场景足够4join()拼回归一化字符串anp成为Map的键PAN和nap都会得到anp这正是分组的依据。3.2map.set的覆盖语义是去重的关键map.set(sorted, word);依据 Map and Set 章节 对map.set(key, value)的说明该方法按键存储值。对同一键第二次set时旧值被新值覆盖键的数量不增加。因此遍历完整数组后每个归一化键下至多保留一个单词——正好满足从每一组变位词中只保留一个单词。保留哪一个取决于该组最后一个被遍历到的单词而题目明确无论保留哪一个都可以。3.3 结果提取Array.from(map.values())map.values()返回一个可迭代的键值迭代器values iterable而我们只需要值、不需要键因此用Array.from(...)把迭代器转为数组返回。也可等价写作return [...map.values()];两种写法在语义上完全等价Array.from在教程中作为标准示例出现展开语法[...]则更简洁。4. 解法二用普通对象替代Map官方解答特别指出本任务中键是字符串因此也可以用普通对象实现。仓库 02-filter-anagrams/solution.md 给出的对象版本function aclean(arr) { let obj {}; for (let i 0; i arr.length; i) { let sorted arr[i].toLowerCase().split().sort().join(); obj[sorted] arr[i]; } return Object.values(obj); } let arr [nap, teachers, cheaters, PAN, ear, era, hectares]; alert( aclean(arr) );与Map版本逐点对照方面Map版本对象版本分组容器new Map(){}写入map.set(sorted, word)obj[sorted] arr[i]同键覆盖结果提取Array.from(map.values())Object.values(obj)适用前提任意类型的键键必须是字符串或可转成字符串需要留意对象版本的一个已知语义普通对象的键会被隐式转成字符串。本任务中归一化键本来就是字符串所以没有问题但如果键可能包含__proto__之类的特殊字符串用Map更安全Map不会受原型链污染影响。这正是 Map and Set 章节 反复强调的Map相比Object的核心差异之一——键不会被转换为字符串任何类型都可以作为键。5. 仓库中的单元测试验证该练习配套了基于 Mocha/Chai 风格的单元测试位于 _js.view/test.jsfunction intersection(arr1, arr2) { return arr1.filter(item arr2.includes(item)); } describe(aclean, function() { it(returns exactly 1 word from each anagram set, function() { let arr [nap, teachers, cheaters, PAN, ear, era, hectares]; let result aclean(arr); assert.equal(result.length, 3); assert.equal(intersection(result, [nap, PAN]).length, 1); assert.equal(intersection(result, [teachers, cheaters, hectares]).length, 1); assert.equal(intersection(result, [ear, era]).length, 1); }); it(is case-insensitive, function() { let arr [era, EAR]; assert.equal(aclean(arr).length, 1); }); });测试覆盖了题目要求的两个核心不变量可作为自测清单每组恰好保留一个对三个变位词组分别求交集每组在结果中恰好命中 1 个词且总长度为 3大小写不敏感[era, EAR]去重后长度为 1。实现只要满足这两个断言aclean就是正确的——测试并不关心具体保留哪一组中的哪一个词与题目保留哪个不限的表述一致。6. 复杂度分析与边界情况6.1 时间复杂度设数组长度为n每个单词平均长度为m排序耗时每个单词O(m log m)整体O(n · m log m)Map.set与Object赋值均为近似O(1)结果提取O(n)。整体复杂度由排序主导。相比每遇到一个单词就与已保留单词逐一比对是否为变位词的O(n²)朴素做法归一化键方案在n较大时优势明显。6.2 边界情况与注意点空数组循环不执行map.values()为空返回[]行为正确重复单词同一个单词出现多次也会被覆盖去重如[ab, ab]结果长度为 1这符合同一组变位词只保留一个的语义大小写混合toLowerCase()已统一处理如Era与era归入同一键多字节字符split()按 UTF-16 码元拆分对 emoji、组合字符等可能产生意外结果若输入包含这类字符需改用Array.from(word)或[...word]按码点拆分。英文单词场景下无此问题。7. 思路延伸同族练习与通用分组模式该章节还配有一个姊妹练习 01-array-unique-map——Filter unique array members要求用Set实现unique(arr)数组去重其参考实现与测试同样位于该目录的_js.view/下。二者构成了按归一化键分组这一模式的两种典型应用aclean本任务把每个元素变换出归一化键排序字符串用Map/对象按键覆盖实现每组保留一个unique直接用Set的每个值只出现一次特性完成全量去重。把元素 → 归一化键的映射抽象出来该模式可以直接迁移到更多场景// 通用模板按归一化键分组去重 function groupByKey(arr, toKey, keep) { let map new Map(); for (let item of arr) { let key toKey(item); map.set(key, item); // 同键覆盖每组仅保留一个 } return Array.from(map.values()); }例如按字符串排序后的形式分组的变位词分组、按取整后数值分组的去重、按URL 的规范化形式分组的链接去重等都是同一思路的直接套用。8. 总结Filter anagrams是 Map and Set 章节中最能体现Map键语义的练习之一其方法论可归纳为三步归一化word.toLowerCase().split().sort().join()把变位词折叠为同一字符串分组覆盖map.set(sorted, word)借助同键覆盖保证每组只留一个提取结果Array.from(map.values())只取值、忽略键。无论选择Map还是普通对象实现核心都是归一化键 同键覆盖。掌握这一模式不仅可以通过本练习的单元测试也能直接用于更复杂的按特征分组算法题中。赞分享文档/教程前端【免费下载链接】en.javascript.infoModern JavaScript Tutorial项目地址https://gitcode.com/gh_mirrors/en/en.javascript.info点击查看免费下载相关推荐json-render 中 Remotion Composition 的定义与动态元数据从 Root.tsx 到 Timeline Specjson render 中 Remotion Composition 的定义与动态元数据从 Root.tsx 到 Timeline Spec 本文围绕 ski文档/教程前端Modern JavaScript Tutorial 数组实战用 sumInput() 实现 prompt 循环输入求和Modern JavaScript Tutorial 数组实战用 sumInput 实现 prompt 循环输入求和 导读 sumInput 是 Modern文档/教程前端Apache Beam Go SDK 实战用 ParDo 与 DoFn 实现 Filter 过滤变换Apache Beam Go SDK 实战用 ParDo 与 DoFn 实现 Filter 过滤变换 本教程基于 Apache Beam 官方 Katas 课大数据批处理流处理数据工程上一篇Python分布式追踪VizTracer与OpenTelemetry集成方案下一篇Eclipse Che与Elastic APM集成分布式应用性能追踪创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考