使用 Set 来提高程式码的效能

使用 Set 来提高程式码的效能

如何使用 Set 来提高程式码的效能

为了保证的可读性,本文采用意译而非直译。

我确信有很多开发人员坚持使用基本的全域性物件:数字,字串,物件,阵列和布林值。对于许多用例,这些都是需要的。 但是如果想让你的程式码尽可能快速和可扩充套件,那么这些基本型别并不总是足够好。

在本文中,我们将讨论JS 中Set物件如何让程式码更快—特别扩充套件性方便。 Array 和Set工作方式存在大量的交叉。但是使用Set会比Array在程式码执行速度更有优势。

想阅读更多优质文章请猛戳GitHub部落格,一年百来篇优质文章等着你!

Set 有何不同

最根本的区别是阵列是一个索引集合,这说明阵列中的资料值按索引排序。

const arr = [A, B, C, D];

console.log(arr.indexOf(A)); // Result: 0

console.log(arr.indexOf(C)); // Result: 2

复制程式码

相比之下,set是一个键的集合。set不使用索引,而是使用键对资料排序。set 中的元素按插入顺序是可迭代的,它不能包含任何重复的资料。换句话说,set中的每一项都必须是惟一的。

主要的好处是什么

set 相对于阵列有几个优势,特别是在执行时间方面:

检视元素:使用indexOf()或includes()检查阵列中的项是否存在是比较慢的。删除元素:在Set中,可以根据每项的的 value 来删除该项。在阵列中,等价的方法是使用基于元素的索引的splice()。与前一点一样,依赖于索引的速度很慢。储存 NaN:不能使用indexOf()或 includes() 来查询值 NaN,而 Set 可以储存此值。删除重复项:Set物件只储存惟一的值,如果不想有重复项存在,相对于阵列的一个显著优势,因为阵列需要额外的程式码来处理重复。时间复杂度?

阵列用来搜寻元素的方法时间复杂度为0(N)。换句话说,执行时间的增长速度与资料大小的增长速度相同。

相比之下,Set用于搜寻、删除和插入元素的方法的时间复杂度都只有O(1),这意味着资料的大小实际上与这些方法的执行时间无关。

Set 究竟有多快?

虽然执行时间可能会有很大差异,具体取决于所使用的系统,所提供资料的大小以及其他变数,但我希望我的测试结果能够让你真实地了解Set的速度。 我将分享三个简单的测试和我得到的结果。

准备测试

在执行任何测试之前,建立一个数组和一个 Set,每个阵列和 Set 都有100万个元素。为了简单起见,我从0开始,一直数到999999。

let arr = [], set = new Set(), n = 1000000;

for (let i = 0; i arr.push(i);

set.add(i);

}

复制程式码

测试1:查询元素

我们搜寻数字123123

let result;

console.time(\Array\);

result = arr.indexOf(123123) !== -1;

console.timeEnd(\Array\);

console.time(\Set\);

result = set.has(123123);

console.timeEnd(\Set\);

复制程式码

Array: 0.173msSet: 0.023msSet 速度快了7.54倍

测试2:新增元素

console.time(\Array\);

arr.push(n);

console.timeEnd(\Array\);

console.time(\Set\);

set.add(n);

console.timeEnd(\Set\);

复制程式码

Array: 0.018msSet: 0.003msSet 速度快了6.73倍

测试3:删除元素

最后,删除一个元素,由于阵列没有内建方法,首先先建立一个辅助函式:

const deleteFromArr = (arr, item) => {

let index = arr.indexOf(item);

return index !== -1 && arr.splice(index, 1);

};

复制程式码

这是测试的程式码:

console.time(\Array\);

deleteFromArr(arr, n);

console.timeEnd(\Array\);

console.time(\Set\);

set.delete(n);

console.timeEnd(\Set\);

复制程式码

Array: 1.122msSet: 0.015msSet 速度快了74.13倍

总的来说,我们可以看到,使用Set 极大地改善执行时间。再来看看一些Set有用的实际例子。

案例1:从阵列中删除重复的值

如果想快速地从阵列中删除重复的值,可以将其转换为一个 Set。这是迄今为止过滤惟一值最简洁的方法:

const duplicateCollection = [\A\, \B\, \B\, \C\, \D\, \B\, \C\];

// 将阵列转换为 Set

let uniqueCollection = new Set(duplicateCollection);

console.log(uniqueCollection) // Result: Set(4) {A, B, C, D}

// 值储存在阵列中

let uniqueCollection = [...new Set(duplicateCollection)];

console.log(uniqueCollection) // Result: [A, B, C, D]

复制程式码

案例2:Google面试问题

问题:

给定一个整数无序阵列和变数 sum,如果存在阵列中任意两项和使等于 sum 的值,则返回true。否则,返回false。例如,阵列[3,5,1,4]和 sum = 9,函式应该返回true,因为4 + 5 = 9。

解答

解决这个问题的一个很好的方法是遍历阵列,建立 Set储存相对差值。

当我们遇到3时,我们可以把6加到Set中, 因为我们知道我们需要找到9的和。然后,每当我们接触到阵列中的新值时,我们可以检查它是否在 Set 中。当遇到5时,在 Set 加上4。最后,当我们最终遇到4时,可以在Set中找到它,就返回true。

const findSum = (arr, val) => {

let searchValues = new Set();

searchValues.add(val - arr[0]);

for (let i = 1, length = arr.length; i let searchVal = val - arr[i];

if (searchValues.has(arr[i])) {

return true;

} else {

searchValues.add(searchVal);

}

};

return false;

};

复制程式码

简洁的版本:

const findSum = (arr, sum) =>

arr.some((set => n => set.has(n) !set.add(sum - n))(new Set));

复制程式码

因为Set.prototype.has()的时间复杂度仅为O(1),所以使用 Set 来代替阵列,最终使整个解决方案的线性执行时为O(N)。

如果使用 Array.prototype.indexOf()或Array.prototype.includes(),它们的时间复杂度都为 O(N),则总执行时间将为O(N²),慢得多!

猜你喜欢