JavaScript 的 Set 对象支持并集、交集、差集等更多集合运算

JavaScript 中的 Set 对象(用于存储唯一值的集合)的功能将会得到增强,增加一些在数学和其他编程语言中常见的强大的集合运算方法。

  • JavaScript 中的 Set: 目前,JavaScript 的 Set 对象提供了一些基本功能,比如添加 (add)、删除 (delete)、检查是否存在 (has) 以及遍历唯一值。但是,它缺少内置的方法来执行集合运算。

  • 集合运算: 上面提到的运算是集合论中的基本概念,在编程中也有广泛的应用:

    • 并集(Union): 两个集合的并集包含两个集合中的所有元素(或两者都有的元素)。例如:1, 2} ∪ {2, 3} = {1, 2, 3
    • 交集(Intersection): 两个集合的交集仅包含两个集合共有的元素。例如:1, 2} ∩ {2, 3} = {2
    • 差集(Difference): 两个集合的差集(A - B)包含存在于集合 A 但不存在于集合 B 中的元素。例如:1, 2} - {2, 3} = {1
    • 对称差集(Symmetric Difference): 包含只存在于其中一个集合,而不存在于两个集合的交集中的元素。例如:1, 2} Δ {2, 3} = {1, 3(通常在原文的 "more" 中暗示了这一点。)
  • 虽然确切的语法可能因最终规范而异,但以下是一个如何使用这些操作的示例:

    const setA = new Set([1, 2, 3]);
    const setB = new Set([3, 4, 5]);
    
    const union = setA.union(setB); // {1, 2, 3, 4, 5}
    const intersection = setA.intersection(setB); // {3}
    const difference = setA.difference(setB); // {1, 2}
        const symmetricDifference = setA.symmetricDifference(setB); // {1, 2, 4, 5}
    
        console.log(union,intersection,difference,symmetricDifference)

file

JavaScript 的 Set 是在 ES2015 规范中引入的,但它一直显得不够完善。而现在,这种情况即将改变。

Set 是一种值的集合,其中每个值只能出现一次。在 ES2015 中的 Set 功能主要围绕着创建集合、向集合添加值、从集合中移除值以及检查值是否属于集合。如果你想对多个集合进行操作或比较,就需要自己编写相应的函数。不过,值得庆幸的是,负责 ECMAScript 规范的 TC39 委员会以及浏览器厂商们正在改进这一点。现在我们可以看到 JavaScript 实现中新增了诸如 union(并集)、intersection(交集)和 difference(差集)等函数。

在深入了解这些新功能之前,让我们先回顾一下 JavaScript 的 Set 目前能做什么,然后再探索新增的 Set 方法及其在各大 JavaScript 引擎中的支持情况。

JavaScript Set 的功能有哪些?

创建 Set

你可以在构造 Set 时不传入任何参数,从而创建一个空的 Set;也可以传入一个可迭代对象(例如数组)来初始化 Set。

const languages = new Set(["JavaScript", "TypeScript", "HTML", "JavaScript"]);

Set 只包含唯一值,因此上面的 Set 实例中只有三个成员。你可以通过 size 属性检查成员数量:

languages.size;
// => 3

添加元素

使用 add 方法可以向 Set 中添加元素。如果尝试添加已经存在的元素,不会发生任何变化:

languages.add("JavaScript"); // 不会重复添加
languages.add("CSS");        // 新元素

languages.size;
// => 4

删除元素

使用 delete 方法可以从 Set 中删除元素:

languages.delete("TypeScript");

languages.size;
// => 3

检查元素是否存在

使用 has 方法可以检查某个值是否在 Set 中。这种检查的时间复杂度为 O(1),而对数组进行相同操作的复杂度为 O(n)。因此,在需要频繁检查值是否存在时,使用 Set 是一种高效且简洁的选择:

languages.has("JavaScript");
// => true

languages.has("TypeScript");
// => false

遍历 Set

你可以通过 forEach 方法或 for...of 循环遍历 Set 的元素。元素的遍历顺序与添加顺序相同:

languages.forEach(element => console.log(element));
// 输出顺序为 "JavaScript", "HTML", "CSS"

此外,你还可以通过 keysvalues(两者实际等价)以及 entries 方法获取 Set 的迭代器。

清空 Set

使用 clear 方法可以清空整个 Set:

languages.clear();

languages.size;
// => 0

然而,这种实现缺少对多个 Set 之间进行操作的功能。例如:

  • 你可能需要创建一个包含两个 Set 所有元素的集合(即两个 Set 的并集)。
  • 查找两个 Set 的共同元素(交集)。
  • 或者获取一个 Set 中存在而另一个 Set 中不存在的元素(差集)。

直到最近,这些操作还需要开发者自行编写相关函数来实现。

Set 新增功能有哪些?

Set 方法的提案为 Set 实例新增了以下方法:
union(并集)、intersection(交集)、difference(差集)、symmetricDifference(对称差集)、isSubsetOf(子集判断)、isSupersetOf(超集判断)、和 isDisjointFrom(互斥判断)。

其中部分方法与 SQL 的某些连接(join)操作类似,以下我们将结合代码示例和直观对比来说明这些新功能的作用。

这些代码示例可以在 Chrome 122+Safari 17+ 中运行。


Set.prototype.union(other)

并集是包含两个集合中所有元素的集合。

const frontEndLanguages = new Set(["JavaScript", "HTML", "CSS"]);
const backEndLanguages = new Set(["Python", "Java", "JavaScript"]);

const allLanguages = frontEndLanguages.union(backEndLanguages);
// => Set {"JavaScript", "HTML", "CSS", "Python", "Java"}

在这个例子中,前两个集合中的所有语言都包含在第三个集合中。和其他向 Set 添加元素的方法一样,重复值会被自动移除。

这相当于 SQL 的 FULL OUTER JOIN

file

Set.prototype.intersection(other)

交集是包含两个集合中同时存在的所有元素的集合。

const frontEndLanguages = new Set(["JavaScript", "HTML", "CSS"]);
const backEndLanguages = new Set(["Python", "Java", "JavaScript"]);

const frontAndBackEnd = frontEndLanguages.intersection(backEndLanguages);
// => Set {"JavaScript"}

在这个例子中,只有 "JavaScript" 同时存在于两个集合中,因此交集仅包含 "JavaScript"

交集相当于 SQL 的 INNER JOIN

file

Set.prototype.difference(other)

差集是从第一个集合中移除在第二个集合中出现的所有元素后剩下的部分。

const frontEndLanguages = new Set(["JavaScript", "HTML", "CSS"]);
const backEndLanguages = new Set(["Python", "Java", "JavaScript"]);

const onlyFrontEnd = frontEndLanguages.difference(backEndLanguages);
// => Set {"HTML", "CSS"}

const onlyBackEnd = backEndLanguages.difference(frontEndLanguages);
// => Set {"Python", "Java"}

在上面的例子中,从前端语言集合中移除后端语言会删除 "JavaScript",结果返回 "HTML""CSS"。相反,从后端语言集合中移除前端语言会返回 "Python""Java"

差集类似于 SQL 的 LEFT JOIN

file

Set.prototype.symmetricDifference(other)

对称差集是两个集合中不同时出现的元素组成的集合。

const frontEndLanguages = new Set(["JavaScript", "HTML", "CSS"]);
const backEndLanguages = new Set(["Python", "Java", "JavaScript"]);

const onlyFrontEnd = frontEndLanguages.symmetricDifference(backEndLanguages);
// => Set {"HTML", "CSS", "Python", "Java"}

const onlyBackEnd = backEndLanguages.symmetricDifference(frontEndLanguages);
// => Set {"Python", "Java", "HTML", "CSS"}

对称差集的结果集合包含两个集合中独有的元素,而不是两者都包含的元素。

对称差集相当于 SQL 的 FULL OUTER JOIN,但排除了两个集合中共同的部分。

file

Set.prototype.isSubsetOf(other)

如果第一个集合的所有元素都出现在第二个集合中,则第一个集合是第二个集合的子集

const frontEndLanguages = new Set(["JavaScript", "HTML", "CSS"]);
const declarativeLanguages = new Set(["HTML", "CSS"]);

declarativeLanguages.isSubsetOf(frontEndLanguages);
// => true

frontEndLanguages.isSubsetOf(declarativeLanguages);
// => false

frontEndLanguages.isSubsetOf(frontEndLanguages);
// => true

一个集合始终是自身的子集。

Set.prototype.isSupersetOf(other)

如果第二个集合的所有元素都出现在第一个集合中,则第一个集合是第二个集合的超集,与子集的关系相反。

const frontEndLanguages = new Set(["JavaScript", "HTML", "CSS"]);
const declarativeLanguages = new Set(["HTML", "CSS"]);

declarativeLanguages.isSupersetOf(frontEndLanguages);
// => false

frontEndLanguages.isSupersetOf(declarativeLanguages);
// => true

frontEndLanguages.isSupersetOf(frontEndLanguages);
// => true

一个集合始终是自身的超集。

Set.prototype.isDisjointFrom(other)

如果两个集合中没有任何相同的元素,则它们是互斥集合(disjoint)。

const frontEndLanguages = new Set(["JavaScript", "HTML", "CSS"]);
const interpretedLanguages = new Set(["JavaScript", "Ruby", "Python"]);
const compiledLanguages = new Set(["Java", "C++", "TypeScript"]);

interpretedLanguages.isDisjointFrom(compiledLanguages);
// => true

frontEndLanguages.isDisjointFrom(interpretedLanguages);
// => false

在这个例子中,解释型语言和编译型语言集合没有任何交集,因此它们是互斥集合。前端语言集合和解释型语言集合共享 "JavaScript",因此它们不是互斥集合。


通过这些新增的方法,JavaScript 的 Set 实例现在能够更方便地进行集合间的操作,极大地提升了使用集合进行复杂操作的效率和可读性。

参考链接

Comments

No comments yet. Why don’t you start the discussion?

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注