news 2026/9/17 9:17:53

es-toolkit 的 xorBy:基于映射函数的两数组对称差集详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
es-toolkit 的 xorBy:基于映射函数的两数组对称差集详解

es-toolkit 的 xorBy:基于映射函数的两数组对称差集详解

【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit

本指南围绕 es-toolkit 数组工具库中的xorBy函数展开,讲解它如何在"先通过映射函数变换、再比较"的语义下计算两个数组的对称差集(symmetric difference),并结合 src/array/xorBy.ts 的源码实现与 src/array/xorBy.spec.ts 的测试用例,说明其使用场景、参数约束、边界行为与底层原理。读完本文,你将能够熟练使用xorBy处理对象数组、按任意维度去重比较,并理解它与xorunionByintersectionBydifferenceBy等函数的组合关系。

xorBy 是什么:带映射基准的对称差集

在集合论中,两个集合的对称差集是指"仅属于其中一个集合、但不同时属于两个集合"的元素集合。es-toolkit 的xorBy就是这一概念的数组实现,特殊之处在于:它不直接比较元素本身,而是先通过用户提供的mapper函数把每个元素变换成比较基准值,再基于这些基准值计算对称差集

const result = xorBy(arr1, arr2, mapper);

这在处理对象数组时尤为实用——两个数组中的对象引用各不相同,直接比较毫无意义,但提取出idlength等字段后即可准确判断"哪些记录只存在于一边"。

基本用法

import { xorBy } from 'es-toolkit/array'; // 按对象的 id 求对称差集 xorBy([{ id: 1 }, { id: 2 }], [{ id: 2 }, { id: 3 }], obj => obj.id); // Returns: [{ id: 1 }, { id: 3 }] // 按字符串长度求对称差集 xorBy(['apple', 'banana'], ['grape', 'cherry', 'apple'], str => str.length); // Returns: [] (所有长度都出现重复)

第一个例子中,id为 2 的对象在两个数组中同时出现,属于交集,被剔除;id为 1、3 的对象各只出现在一个数组中,被保留。第二个例子中,四个字符串的长度分别为 5、6、5、6,全部在两侧重复,因此对称差集为空。

映射结果相同的元素被视作同一个

xorBy的一个关键语义是:凡是映射函数输出相同值的元素,在比较时都视作同一个元素,因此无需关心元素原始值是否相等。

import { xorBy } from 'es-toolkit/array'; xorBy([1, 2, 3, 4], [3, 4, 5, 6], n => n % 3); // Returns: [] (所有余数均出现重复)

这里映射结果为n % 3:第一个数组的余数为 1、2、0、1,第二个数组的余数为 0、1、2、0,集合 {0, 1, 2} 在两数组中完全重合,故结果为空数组。可见xorBy关注的是"映射后的取值域"是否重合,而非元素本身。

参数与返回值

xorBy的完整签名(见 src/array/xorBy.ts):

export function xorBy<T, U>(arr1: readonly T[], arr2: readonly T[], mapper: (item: T) => U): T[]
  • arr1readonly T[]):参与比较的第一个数组,只读,函数不会修改它。
  • arr2readonly T[]):参与比较的第二个数组,同样只读。
  • mapper(item: T) => U):把每个元素转换为比较基准值的函数,其返回类型U即比较键的类型。
  • 返回值(T[]):一个新数组,包含基于映射结果计算出的对称差集元素;原数组不被改动。

由于泛型TU的存在,mapper可以返回任意可比较类型——数字、字符串、布尔值均可,Set/Map结构对它们使用 SameValueZero 语义判等。

源码原理:union、intersection、difference 的三段式组合

xorBy的实现极为精简,其核心思想可以概括为一条公式:

对称差集 = 并集 − 交集

// 源码:src/array/xorBy.ts export function xorBy<T, U>(arr1: readonly T[], arr2: readonly T[], mapper: (item: T) => U): T[] { const union = unionBy(arr1, arr2, mapper); const intersection = intersectionBy(arr1, arr2, mapper); return differenceBy(union, intersection, mapper); }

三个步骤分别委托给三个同族函数:

  1. unionBy(arr1, arr2, mapper):先合并两个数组,再按映射值去重,得到"映射基准下的并集"。src/array/unionBy.ts 的实现是uniqBy(arr1.concat(arr2), mapper),而uniqBy(见 src/array/uniqBy.ts)使用Map记录映射值并保留首次出现的元素,保证去重后仍按原顺序排列。
  2. intersectionBy(arr1, arr2, mapper):找出"映射基准下同时存在于两个数组的元素"。src/array/intersectionBy.ts 先把第二个数组整体映射后放入Set,再遍历第一个数组,命中即保留并从 Set 中删除该键,因此重复元素只计一次。
  3. differenceBy(union, intersection, mapper):从并集中剔除交集,src/array/differenceBy.ts 同样是"先映射成 Set、再过滤"的写法,最终留下的就是只属于某一侧的对称差集元素。

这一三段式设计与无映射版本xor完全同构——src/array/xor.ts 的实现是difference(union(arr1, arr2), intersection(arr1, arr2))。可以推断,xorBy就是把xorunion/intersection/difference全部替换为带mapper*By变体,从而把"按值比较"升级为"按映射基准比较"。

边界行为与测试佐证

src/array/xorBy.spec.ts 使用 Vitest 覆盖了多种边界场景,可作为理解函数行为的权威参考:

输入mapper期望输出说明
[1,2,3,4]/[3,4,5,6]恒等函数[1,2,5,6]常规对称差集
[{id:1},{id:2}]/[{id:2},{id:3}]obj => obj.id[{id:1},{id:3}]对象按 id 比较
[1,2,3]/[4,5,6]恒等函数[1,2,3,4,5,6]无交集时退化为并集
[1,2,3]/[1,2,3]恒等函数[]完全重合时为空集
[]/[1,2,3]恒等函数[1,2,3]空数组与任意数组的对称差集即对方
[1,2,3]/[]恒等函数[1,2,3]同上,方向无关

这些用例同时验证了对称差集的交换律xorBy(a, b)xorBy(b, a)结果一致)以及"空数组参与时返回另一数组内容"的退化行为。其中对象按id去重的用例直接对应文档示例,恒等映射用例则说明xorBy(arr1, arr2, x => x)xor(arr1, arr2)在数值数组上等价。

与同族函数的对比与选型

xorBy位于 es-toolkit 数组工具家族的"集合运算"梯队中,与其关系最密切的是:

  • xor:无映射版本,直接比较元素值,适合原始类型数组;
  • unionBy/intersectionBy/differenceBy:分别计算并集、交集、差集(均为非对称),xorBy恰是它们的组合;
  • xorWith(另一变体):支持自定义比较器而非映射函数,适合需要两两比较的复杂场景。

选型建议:当比较基准是"由元素派生的某个键"(如对象属性、取模结果、字符串长度)时选xorBy;当只需按元素本身判等时选xor即可获得更少的一次映射开销。

使用注意事项

  • 映射函数应保持纯函数性mapper会在unionByintersectionBydifferenceBy三个阶段被多次调用,若依赖外部可变状态或产生副作用,可能得到不一致结果。
  • 比较基于映射值而非原值:两个原始值不同、但映射结果相同的元素会被当作重复项处理,这与文档"映射函数结果相同的元素视为一个"的语义一致。
  • 不会修改原数组:所有中间结果均为新数组,arr1arr2始终保持只读,可放心用于函数式编程流水线。

小结

xorBy用最简洁的三行组合实现了"基于映射基准的对称差集",其unionBy − intersectionBy的架构既保证了与无映射版本xor语义的一致性,又通过Map/Set保持了 O(n) 级别的查找效率。无论是按对象id同步数据、按字符串长度归类去重,还是按任意维度求"只属于一边"的元素,它都是 es-toolkit 中开箱即用的标准答案。

【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit

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

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

VS2022将C语言程序打包成exe:从配置到分发的完整实操指南

用VS2022把C语言文件打包成exe&#xff0c;发给朋友直接就能跑&#xff08;全套实操&#xff09;前几天一个学弟在微信上找我&#xff0c;说他用VS2022写了个C语言小工具&#xff0c;想发给女朋友电脑上用&#xff0c;结果对方一运行就报错&#xff0c;不是缺dll就是窗口一闪而…

作者头像 李华
网站建设 2026/9/17 9:13:50

力扣459与1768:KMP字符串匹配与双指针模拟刷题实战

1. 两道题的整体定位与刷题思路先说结论&#xff1a;今天这组合我挺满意。459和1768&#xff0c;一个考的是字符串交替合并的模拟能力&#xff0c;另一个考的是对字符串匹配底层原理的理解。难度上&#xff0c;1768属于不折不扣的"力扣简单题"&#xff0c;459虽然也被…

作者头像 李华
网站建设 2026/9/17 9:12:02

车载音频动态路由技术:智能座舱的实时混音方案

1. 车载音频系统动态路由技术背景现代智能座舱的音频架构正在经历从传统固定路由到动态智能分配的演进过程。想象一下这样的场景&#xff1a;当你在高速公路上使用导航时&#xff0c;系统需要同时处理来自蓝牙电话、音乐播放和危险路况预警的音频流。传统固定优先级方案会导致关…

作者头像 李华
网站建设 2026/9/17 9:11:55

Xbox Kinect v2 DIY 3D扫描系统实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 9:08:35

Java实现企业奖金阶梯计算方案与优化

1. 企业奖金计算需求解析企业奖金计算是财务系统中常见的业务场景&#xff0c;特别是在销售提成、绩效奖励等环节。这个需求的核心是根据不同的利润区间&#xff0c;按照阶梯式算法计算应发放的奖金金额。这种分段计算方式在财务领域被称为"超额累进税率"算法&#x…

作者头像 李华
网站建设 2026/9/17 9:07:25

VS 2022 报错排查指南:分层定位与高频错误速查

1. VS 2022报错处理的底层思路与排查框架VS 2022 报错这件事&#xff0c;说大不大说小不小。用了几年下来我的感受是&#xff1a;真正让人抓狂的从来不是那一行红字本身&#xff0c;而是同一行红字在不同项目、不同机器上表现完全不同&#xff0c;你照着搜索出来的答案抄一遍&a…

作者头像 李华