D3.js d3-array 集运算详解:difference、union、intersection 与 superset、subset、disjoint 的完整用法
【免费下载链接】d3Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:项目地址: https://gitcode.com/GitHub_Trending/d3/d3
本文基于 D3.js 官方文档中的 Set operations 一章,系统讲解 d3-array 提供的六个逻辑集运算函数——d3.difference(差集)、d3.union(并集)、d3.intersection(交集)、d3.superset(超集判断)、d3.subset(子集判断)和d3.disjoint(不相交判断)。读完本文,你将掌握这些函数的签名、返回值语义与取值顺序规则,理解其返回类型InternSet与普通Set的差异,并能在数据清洗、用户群差异分析等实际场景中直接用任意可迭代对象完成集合逻辑运算。
六个集运算函数总览
d3-array 将集合逻辑运算抽象为对"任意可迭代对象(any iterable)"操作的一组函数,这是该章节的原始定位:"Logical set operations for any iterable."。六个函数分为两类:
| 函数 | 签名 | 返回值 | 用途 |
|---|---|---|---|
d3.difference | difference(*iterable*, ...*others*) | InternSet | 差集:iterable中不在任何others里的值 |
d3.union | union(...*iterables*) | InternSet | 并集:所有iterables中出现的(去重后的)全部值 |
d3.intersection | intersection(...*iterables*) | InternSet | 交集:在所有iterables中都出现的(去重后的)值 |
d3.superset | superset(*a*, *b*) | boolean | a是否包含b的全部值(a是b的超集) |
d3.subset | subset(*a*, *b*) | boolean | a的每个值是否都在b中(a是b的子集) |
d3.disjoint | disjoint(*a*, *b*) | boolean | a与b是否没有任何共享值 |
这组函数通过本仓库的 src/index.js 以export * from "d3-array"的形式整体重导出,因此在安装d3包后直接d3.union(...)等调用即可;从 package.json 可以看到,当前 d3 版本为 7.9.0,依赖d3-array: ^3.2.4,集运算函数属于该依赖包的 API 面。完整索引还可参考 docs/api.md 中 "Sets" 一节。
d3.difference:计算差集
d3.difference(*iterable*, ...*others*)接收一个基集iterable和任意数量的其他可迭代对象others,返回一个新的InternSet,其中包含iterable中不存在于任何others的每一个值。
官方文档给出的示例:
d3.difference([0, 1, 2, 0], [1]) // Set {0, 2}基集中虽然0出现了两次,但返回结果只保留一份;1因为在第二个参数[1]中出现而被剔除。
典型场景是"排除已处理的记录":给定一批全部用户 ID 和一个黑名单,d3.difference(allIds, blacklist)就是需要保留的用户集合。由于第二个及之后的参数是变参(...*others),也可以同时排除多个来源:
// 从候选集中排除 A、B 两个来源的值 const candidates = d3.difference([10, 20, 30, 40], [20], [40, 50]); // Set {10, 30}d3.union:计算并集
d3.union(...*iterables*)对任意多个可迭代对象求并集,返回的InternSet包含在所有给定iterables中出现过的每一个去重后的值。一个关键规则是:结果集中值的顺序基于它们在给定iterables中首次出现的先后(order based on first occurrence)。
官方文档示例:
d3.union([0, 2, 1, 0], [1, 3]) // Set {0, 2, 1, 3}注意结果的顺序{0, 2, 1, 3}不是数值升序,而是按照0、2、1来自第一个数组的首次出现顺序,随后补上仅在第二个数组中首次出现的3。这意味着并集结果保留了原始数据的出现次序,而非排序后的次序,这在需要保持记录原始顺序(如时间序列中的事件 ID)时是有意义的。
// 合并多张表中的字段名,保持首次出现顺序 const fields = d3.union(["id", "name"], ["name", "email", "id"]); // Set {id, name, email}d3.intersection:计算交集
d3.intersection(...*iterables*)返回一个InternSet,包含在所有给定iterables中都出现的每一个(去重后的)值;同样地,结果顺序基于首次出现顺序。
官方文档示例:
d3.intersection([0, 2, 1, 0], [1, 3]) // Set {1}只有1同时出现在两个数组中,因此交集为{1}。交集常用于找出多个数据源的公共键,例如多张 CSV 记录共同的站点 ID:
const common = d3.intersection(sitesA, sitesB, sitesC); // 三张表都出现的站点与并集不同,如果某个值只出现在部分输入中,它就会被排除;变参形式允许对两个以上集合求交。
superset、subset 与 disjoint:布尔判断
与前三者返回集合不同,剩余三个函数只做判断,返回布尔值:
d3.superset(*a*, *b*):当a是b的超集时返回true,即b中的每个值也都在a中。
d3.superset([0, 2, 1, 3, 0], [1, 3]) // trued3.subset(*a*, *b*):当a是b的子集时返回true,即a中的每个值也都在b中。注意它与superset互为参数位置上的对偶:d3.subset(x, y)等价于d3.superset(y, x)。
d3.subset([1, 3], [0, 2, 1, 3, 0]) // trued3.disjoint(*a*, *b*):当a与b不相交(disjoint)时返回true,即两个集合没有任何共享值。
d3.disjoint([1, 3], [2, 4]) // true这三个判断函数接受的是"给定可迭代对象",内部按值相等性比较;disjoint尤其适合做"两个筛选条件是否互斥"的快速校验,而不必实际计算交集再检查是否为空。
返回值类型 InternSet:为何不是原生 Set
difference、union、intersection的返回值被文档明确标注为 InternSet,这是 d3 对原生Set的扩展(InternSet/InternMap分别继承自原生Set/Map),其核心能力是绕过 SameValueZero 相等性算法,允许以Date等非原始值(non-primitive values)作为集合元素。
原生Set使用同一引用或 SameValueZero 判定重复,而InternSet在判断相等时会对非原始值调用key函数,默认为object.valueOf。因此在 docs/d3-array/intern.md 中演示的按日期去重场景里,两个内容相同的Date对象会被视为"同一个值":
const dates = new d3.InternSet([ new Date("2022-01-01"), new Date("2022-01-01") // 视为同一个值,集合中仅保留一个 ]);这直接影响集运算的正确性:如果你用原生Set对包含Date的对象数组求union或difference,两个"值相同但引用不同"的日期会被当作两个元素;而 d3 的集运算函数统一返回InternSet,恰好规避了这个问题。文档同时指出d3.group、d3.rollup和d3.index内部也是用InternMap而非原生Map,即 d3 在"按值分组/集合化"这条主线上对非原始值是一致地做了内省化(interning)处理。
实战:在数据管道中做差异分析与覆盖校验
结合上述函数,一个常见的数据管道片段是:从数据源 A 中筛出"新增项"、校验"配置覆盖"、并检查"两组 ID 是否冲突"。由于输入可以是任意可迭代对象(数组、Set、迭代器均可),这些运算可以无缝嵌入已有的 d3 数据处理流:
import * as d3 from "d3"; const lastWeek = [101, 102, 103]; const thisWeek = [102, 103, 104]; // 本周新增的用户(差集) const newUsers = d3.difference(thisWeek, lastWeek); // Set {104} // 本周流失的用户(反方向差集) const lostUsers = d3.difference(lastWeek, thisWeek); // Set {101} // 持续存在的用户(交集) const retained = d3.intersection(thisWeek, lastWeek); // Set {102, 103} // 校验:本周名单是否为上周名单的超集(是否有用户流失) const noLoss = d3.superset(thisWeek, lastWeek); // false // 校验:两个渠道的 ID 是否完全互斥(无重复投放) const exclusive = d3.disjoint(channelA, channelB); // true / false上面的语义均可由本文列出的官方示例直接推出:d3.difference([0, 1, 2, 0], [1])得到Set {0, 2},d3.superset([0, 2, 1, 3, 0], [1, 3])得到true等。使用时的两点注意:一是结果顺序遵循"首次出现顺序"而非排序,需要有序输出时请自行d3.sort([...set]);二是这些函数只按值的内省化相等性比较,对NaN等特殊值以及对象结构(嵌套对象不会被深度比较)的处理仍取决于valueOf的默认行为,若需自定义相等规则,应转向带key函数的 InternSet 构造方式自行组织集合。
环境说明与延伸阅读
- 适用前提:安装 d3 7.x(本仓库为 7.9.0)及其依赖
d3-array ^3.2.4后,六个函数即可通过全局d3命名空间调用;仓库入口 src/index.js 逐模块export *,因此也适合按模块按需引入d3-array。 - 本文所有函数签名与示例均取自 docs/d3-array/sets.md,返回类型与相等性规则参见 docs/d3-array/intern.md,d3-array 各主题入口见 docs/d3-array.md。
- 各函数在上游 d3-array 包中的源码位置(
difference.js、union.js、intersection.js、superset.js、subset.js、disjoint.js)由官方文档给出,可对照上游仓库验证实现细节。
【免费下载链接】d3Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:项目地址: https://gitcode.com/GitHub_Trending/d3/d3
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考