使用 Javascript 数组计算集差的最快或最优雅的方法是什么?

IT技术 javascript arrays set-difference
2021-01-17 03:44:44

AB成为两组。我正在寻找非常快速或优雅的方法来计算它们之间的集差(A - BA \B,取决于您的偏好)。正如标题所说,这两个集合作为 Javascript 数组进行存储和操作。

笔记:

  • 壁虎特有的技巧没问题
  • 我更喜欢坚持使用本机函数(但如果速度更快,我对轻量级库持开放态度)
  • 我见过,但没有测试过,JS.Set(见上一点)

编辑:我注意到关于包含重复元素的集合的评论。当我说“设置”时,我指的是数学定义,这意味着(除其他外)它们不包含重复元素。

6个回答

如果不知道这是否最有效,但也许是最短的

A = [1, 2, 3, 4];
B = [1, 3, 4, 7];

diff = A.filter(function(x) { return B.indexOf(x) < 0 })

console.log(diff);

更新到 ES6:

A = [1, 2, 3, 4];
B = [1, 3, 4, 7];

diff = A.filter(x => !B.includes(x) );

console.log(diff);
@EricBréchemier 现在支持(自 IE 9 起)。Array.prototype.filter是一个标准的 ECMAScript 特性。
2021-03-25 03:44:44
注意:array.filter 不支持跨浏览器(例如不在 IE 中)。@Matt 似乎无关紧要,因为他说“Gecko 特定的技巧是可以的”,但我认为值得一提。
2021-04-07 03:44:44
这是非常缓慢的。O(|A| * |B|)
2021-04-09 03:44:44
在 ES6 中,您可以使用!B.includes(x)代替B.indexOf(x) < 0:)
2021-04-09 03:44:44
+1:不是最有效的解决方案,但绝对简短易读
2021-04-10 03:44:44

好吧,7 年后,使用ES6 的 Set对象很容易(但仍然没有python 的 紧凑A - B),而且据说比indexOf大型数组更快

console.clear();
let a = new Set([1, 2, 3, 4]);
let b = new Set([5, 4, 3, 2]);


let a_minus_b = new Set([...a].filter(x => !b.has(x)));
let b_minus_a = new Set([...b].filter(x => !a.has(x)));
let a_intersect_b = new Set([...a].filter(x => b.has(x))); 

console.log([...a_minus_b]) // {1}
console.log([...b_minus_a]) // {5}
console.log([...a_intersect_b]) // {2,3,4}

因为它是 JavaScript
2021-03-29 03:44:44
@SwiftsNamesake 有一个关于 set 内置方法的提议,有望在 2018 年 1 月github.com/tc39/agendas/blob/master/2018/01.md 中讨论
2021-04-03 03:44:44
对于大型数组,也比 indexOf 快得多。
2021-04-05 03:44:44
为什么 JavaScript 集没有内置并集/相交/差集是我无法理解的......
2021-04-07 03:44:44
我完全同意; 这些应该是在 js 引擎中实现的较低级别的原语。这也超出了我...
2021-04-07 03:44:44

您可以使用一个对象作为地图,以避免线性扫描B每个元素的A作为user187291的回答

function setMinus(A, B) {
    var map = {}, C = [];

    for(var i = B.length; i--; )
        map[B[i].toSource()] = null; // any other value would do

    for(var i = A.length; i--; ) {
        if(!map.hasOwnProperty(A[i].toSource()))
            C.push(A[i]);
    }

    return C;
}

非标准toSource()方法用于获取唯一的属性名称;如果所有元素都已经具有唯一的字符串表示形式(数字就是这种情况),您可以通过删除toSource()调用来加速代码

最短的,使用 jQuery,是:

var A = [1, 2, 3, 4];
var B = [1, 3, 4, 7];

var diff = $(A).not(B);

console.log(diff.toArray());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

not从 3.0.0-rc1 开始,jQuery不再适用于通用对象。github.com/jquery/jquery/issues/3147
2021-03-14 03:44:44
虽然这种方法的代码较少,但它没有对不同算法的空间和时间复杂度以及它用来执行该方法的数据结构提供任何解释。当允许数据放大或内存有限时,开发人员无需评估即可对软件进行工程设计。如果你对大数据集使用这种方法,性能可能仍然未知,直到进一步研究源代码。
2021-03-23 03:44:44
这将返回一个差异对象。
2021-04-01 03:44:44
这只是返回 A 中不在 B 中的元素的数量(在这种情况下为 2)。将 2 转换为数组是毫无意义的......
2021-04-02 03:44:44
这不是一个好主意,添加一个依赖于70K〜第三方库只是要做到这一点,因为同样的事情可以在短短的几行代码,如图其他的答案在这里完成。但是,如果您已经在您的项目中使用 jQuery,这将工作得很好。
2021-04-05 03:44:44

看看这些解决方案中的很多,它们在小案例中做得很好。但是,当你把它们炸到一百万个项目时,时间复杂度就开始变得愚蠢了。

 A.filter(v => B.includes(v))

这开始看起来像一个 O(N^2) 解决方案。由于有一个 O(N) 解决方案,让我们使用它,如果您的 JS 运行时不是最新的,您可以轻松修改为不是生成器。

    function *setMinus(A, B) {
      const setA = new Set(A);
      const setB = new Set(B);

      for (const v of setB.values()) {
        if (!setA.delete(v)) {
            yield v;
        }
      }

      for (const v of setA.values()) {
        yield v;
      }
    }

    a = [1,2,3];
    b = [2,3,4];

    console.log(Array.from(setMinus(a, b)));

虽然这比许多其他解决方案要复杂一些,但当您有大列表时,这会快得多。

让我们快速看一下性能差异,在 0...10,000 之间的 1,000,000 个随机整数集上运行它,我们看到以下性能结果。

setMinus time =  181 ms
    diff time =  19099 ms

function buildList(count, range) {
  result = [];
  for (i = 0; i < count; i++) {
    result.push(Math.floor(Math.random() * range))
  }
  return result;
}

function *setMinus(A, B) {
  const setA = new Set(A);
  const setB = new Set(B);

  for (const v of setB.values()) {
    if (!setA.delete(v)) {
        yield v;
    }
  }

  for (const v of setA.values()) {
    yield v;
  }
}

function doDiff(A, B) {
  return A.filter(function(x) { return B.indexOf(x) < 0 })
}

const listA = buildList(100_000, 100_000_000); 
const listB = buildList(100_000, 100_000_000); 

let t0 = process.hrtime.bigint()

const _x = Array.from(setMinus(listA, listB))

let t1 = process.hrtime.bigint()

const _y = doDiff(listA, listB)

let t2 = process.hrtime.bigint()

console.log("setMinus time = ", (t1 - t0) / 1_000_000n, "ms");
console.log("diff time = ", (t2 - t1) / 1_000_000n, "ms");

@RonKlein 公平点,将代码更新为两组
2021-03-23 03:44:44