news 2026/10/3 8:26:32

Modern JavaScript 教程:用 Map 键值归组实现变位词(Anagram)过滤——`aclean` 解法深度剖析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Modern JavaScript 教程:用 Map 键值归组实现变位词(Anagram)过滤——`aclean` 解法深度剖析
  • 文档/教程
  • 前端

【免费下载链接】en.javascript.info

Modern JavaScript Tutorial

项目地址:https://gitcode.com/gh_mirrors/en/en.javascript.info
点击查看免费下载

导读

本文围绕 Modern JavaScript Tutorial(本仓库 en.javascript.info)中「Map 与 Set」一节的经典练习「过滤变位词」(Filter anagrams 任务)展开,逐行剖析其官方解法 solution.md:通过「按字母排序后的字符串」作为 Map 键,将互为变位词的单词归入同一组并只保留一个代表词。读完本文,你将掌握Map的键去重覆盖语义、split → sort → join字符串规范化的核心套路,以及Map方案与普通对象方案的取舍,并看到仓库内置测试 test.js 如何验证这两项行为。

一、任务背景:什么是变位词,要过滤掉什么

「变位词(Anagram)」指拥有相同数量的相同字母、但排列顺序不同的单词。任务文档 task.md 给出的示例是:

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"

注意两个关键点,它们在后面的解法和测试中都会体现:

  • 大小写不敏感:"PAN"与"nap"视为同一组变位词(注意示例输入中特意混入了大写);
  • 保留哪个单词不限:每组只需留下一个,可以是组内任意成员。

二、核心思路:把单词规范化为「字母排序形式」

官方解法 solution.md 的第一句话就点明了核心思想:

将每个单词拆成字母并排序,排序后的结果对所有变位词是相同的。

对每个单词做「字母排序」后,互为变位词的单词会收敛到同一个规范化形式:

nap, pan -> anp ear, era, are -> aer cheaters, hectares, teachers -> aceehrst

这个规范化字符串就可以作为分组键(key)。这个思路不限于变位词,它是字符串归组类问题(如「去重」「找同构词」)的通用模式:先通过某种确定性变换得到规范形式,再以规范形式为键做一次归组。

三、Map 解法:以排序串为键,覆盖式去重

官方推荐的完整实现如下(与仓库内 solution.js 完全一致):

function aclean(arr) { let map = new Map(); for (let word of arr) { // 拆字母、排序、拼回:所有变位词得到同一个键 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 关键一行:toLowerCase().split('').sort().join('')

字母排序由一行方法链完成,官方文档将其拆解为多行便于理解:

let sorted = word // PAN .toLowerCase() // pan .split('') // ['p','a','n'] .sort() // ['a','n','p'] .join(''); // anp

四个步骤各司其职:

  1. toLowerCase():先把单词统一转为小写,保证"PAN"与"nap"得到同一个规范化键'anp'——这是满足「大小写不敏感」要求的关键;
  2. split(''):按空字符串拆分,把单词打散为字符数组,这样才能调用数组的sort;
  3. sort():默认按 UTF-16 码元升序排序,字母会被排成规范顺序。注意:这里没有传比较函数,因此只适合处理纯英文字母等简单场景(Unicode 变体的注意事项见第五节);
  4. join(''):把排序后的数组拼回字符串,得到规范化键。

3.2 覆盖语义:map.set(sorted, word)自动去重

接下来map.set(sorted, word)把单词存入以规范化键命名的槽位:

map.set(sorted, word);

这里利用了Map的一个关键特性——同键写入是覆盖式的。当后面再次遇到具有相同排序形式的单词时(例如先存了'nap',又遇到'PAN',两者键都是'anp'),set会用新值覆盖旧值。因此遍历完整个数组后,每个规范化键最多只对应一个单词,变位词组天然被压缩成单元素。官方解法原文对此的表述是:

如果之后再次遇到具有相同字母排序形式的单词,它会在 Map 中覆盖同键下的旧值。所以我们总能保证每个字母形式最多对应一个单词。

这正是Map相比「先收集全组再手动去重」的优雅之处:去重不再需要额外的includes或find检查,归组与去重由键的覆盖语义一步完成。

3.3 取回结果:Array.from(map.values())

最后一行把结果取回数组:

return Array.from(map.values());
  • map.values()返回一个按插入顺序迭代值(即单词本身)的可迭代对象,键(排序串)在此处不再需要;
  • Array.from(...)把这个可迭代对象转换为普通数组,正是aclean需要的返回值类型。

关于Map的遍历方法(keys()/values()/entries())及其保持插入顺序的特性,可参见本节主文章 Map and Set 中「Iteration over Map」部分的详细介绍。

四、另一种解法:用普通对象代替 Map

官方解法还给出了一条等价路径:因为规范化键是字符串,普通对象同样可以作为「字符串键 → 单词」的映射使用:

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 版本对象版本
建容器let map = new Map()let obj = {}
循环方式for (let word of arr)for (let i = 0; i < arr.length; i++)
写入map.set(sorted, word)obj[sorted] = arr[i]
取结果Array.from(map.values())Object.values(obj)

两者行为等价:对象的同名属性赋值同样是覆盖式的,Object.values(obj)也能直接取回所有保留的单词。官方文档给出的取舍建议很明确:

这里也可以用普通对象代替 Map,因为键是字符串。

什么时候选哪个?需要键为任意类型(对象、数字、布尔等)、或需要size、has、delete等便捷方法时选Map;本任务键恰好是字符串,对象写法更轻量。Map与Object的能力差异可参考 Map and Set 中的 Summary 对比。

五、仓库测试验证:两组断言锁定行为

仓库为该任务配备了可运行测试 test.js(与 solution.js 同目录),用 Mocha 的describe/it/assert从两个维度验证aclean:

断言一:每组变位词只保留一个

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); // nap/PAN 组只留 1 个 assert.equal(intersection(result, ["teachers", "cheaters", "hectares"]).length, 1); assert.equal(intersection(result, ["ear", "era"]).length, 1); });

其中intersection是测试文件自带的辅助函数,用filter+includes计算两个数组的交集元素数量。断言精确刻画了「每组恰好留一个」的语义。

断言二:大小写不敏感

it("is case-insensitive", function() { let arr = ["era", "EAR"]; assert.equal(aclean(arr).length, 1); });

"era"与"EAR"经toLowerCase()后得到同一规范化键'aer',最终只保留一个单词。这两个断言恰好与任务文档 task.md 中的示例输入和「大小写不敏感」要求一一对应,是解法正确性的直接代码级证据。

六、边界情况与复杂度

边界情况

  • 空字符串与单字母:''.split('')得[],sort()后仍为空数组,join('')得'',一切空串会归到同一个键;单字母单词的排序形式就是它自己。这两种情况都能被上述代码正确处理。
  • 大小写混合:toLowerCase()保证'PAN'、'Pan'、'nap'统一。
  • 非英文字母 / Unicode:sort()默认按 UTF-16 码元排序,对带重音字符(如é)或需要规范化(如fi连字、组合字符)的文本,排序结果可能不够直观;若业务场景需要,可额外考虑用比较函数或 Unicode 规范化做键,但这不是本任务的要求范围。

复杂度

从代码结构可以推断:aclean的时间开销主要来自两处——每个单词做一次O(k log k)的排序(k 为单词平均长度),以及整个数组的O(n)次set操作,因此总体时间复杂度为O(n · k log k),空间复杂度为O(n)(Map 中最多存 n 个键值对)。相比「对每个词再线性扫描已收集单词」的朴素做法,按键归组避免了二次方级别的重复比较。

七、小结:一个可复用的「规范化键」模式

aclean这道题的解法虽然短,却浓缩了两个可迁移到真实工程的知识点:

  1. 规范形式(canonical form):split → sort → join把「字母集合相同但顺序不同」的单词归一为同一个键,这是字符串归组问题的通用预处理手段;
  2. Map 的覆盖式键值语义:map.set(key, value)遇同键自动覆盖,天然实现「每组取一」,配合Array.from(map.values())把结果还原成数组。

同时,仓库中的 solution.md、solution.js 与 test.js 三者共同构成了「任务 → 实现 → 验证」的完整闭环,也是本项目所有练习任务的标准组织方式(参见 01-array-unique-map 同构的任务目录)。建议读者在本地以node直接运行 test.js 与 solution.js,观察两组断言全部通过,即可确认对本题语义的理解无误。

  • 文档/教程
  • 前端

【免费下载链接】en.javascript.info

Modern JavaScript Tutorial

项目地址:https://gitcode.com/gh_mirrors/en/en.javascript.info
点击查看免费下载
上一篇:CVAT AI自动标注怎么用:数据标注完整实操指南
下一篇:FakeLocation:Android应用级虚拟定位完全指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 8:21:42

CMake get_property 命令完全指南:十种作用域属性读取与源码实现解析

构建工具开发工具CLI 【免费下载链接】CMake Mirror of CMake upstream repository 项目地址&#xff1a; https://gitcode.com/gh_mirrors/cm/CMake 点击查看 免费下载 get_property 是 CMake 中读取属性的通用入口命令&#xff0c;它从全局、目录、目标、源文件、测试、缓存…

作者头像 李华
网站建设 2026/10/3 8:17:27

agno Agent 输入输出实用指南:6 个机制控制它说什么、怎么说

agno Agent 输入输出实用指南&#xff1a;6 个机制控制它说什么、怎么说 【免费下载链接】agno Build, run, and manage agent platforms. 项目地址: https://gitcode.com/GitHub_Trending/ag/agno agno 是一个用 Python 构建、运行和管理 Agent 平台的框架。实际用起来…

作者头像 李华