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

Mat*_*all 90 javascript arrays set-difference

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

笔记:

  • 壁虎特有的技巧是可以的
  • 我更喜欢坚持本机功能(但如果速度更快,我会对轻量级库开放)
  • 我见过,但没有经过测试,JS.Set(见前一点)

编辑:我注意到有关包含重复元素的集合的注释.当我说"集合"时,我指的是数学定义,这意味着(除其他外)它们不包含重复元素.

use*_*291 158

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

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

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

console.log(diff);
Run Code Online (Sandbox Code Playgroud)

已更新至ES6:

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

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

console.log(diff);
Run Code Online (Sandbox Code Playgroud)

  • 这很慢.O(| A |*| B |) (36认同)
  • 注意:跨浏览器不支持array.filter(例如,不在IE中).对于@Matt似乎并不重要,因为他说"Gecko特有的技巧是可以的",但我认为值得一提. (10认同)
  • +1:不是最有效的解决方案,但绝对简短易读 (8认同)
  • 在ES6中,您可以使用[`!B.includes(x)`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/includes)而不是`B.indexOf (x)<0` :) (4认同)
  • @WongJiaHau 这可以通过哈希集分摊“O(|A| + |B|)”来完成。 (2认同)

mil*_*lan 70

好吧,7年后,使用ES6的Set对象很容易(但仍然不像蟒蛇A-B那么紧凑),据报道比A - B大型阵列更快:

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}
Run Code Online (Sandbox Code Playgroud)

  • 为什么JavaScript集合没有内置的联合/交叉/差异超出我的... (71认同)
  • 我完全同意; 这些应该是js引擎中实现的低级原语.它也超越了我...... (6认同)
  • @SwiftsNamesake有一个关于设置内置方法的建议,希望在2018年1月被讨论https://github.com/tc39/agendas/blob/master/2018/01.md. (3认同)
  • 对于大型数组,也比 indexOf 快得多。 (2认同)

kob*_*las 23

看看这些解决方案中的许多,它们对于小情况来说效果很好。但是,当你将它们增加到一百万个项目时,时间复杂度开始变得愚蠢。

 A.filter(v => B.includes(v))
Run Code Online (Sandbox Code Playgroud)

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

 A.filter(v => B.includes(v))
Run Code Online (Sandbox Code Playgroud)

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

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

setMinus time =  181 ms
    diff time =  19099 ms
Run Code Online (Sandbox Code Playgroud)

    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)));
Run Code Online (Sandbox Code Playgroud)


Chr*_*oph 15

您可以将对象用作地图,以避免线性扫描B每个元素,Auser187291的答案:

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;
}
Run Code Online (Sandbox Code Playgroud)

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


per*_*ion 9

使用jQuery的最短时间是:

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

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

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

  • 从3.0.0-rc1开始,jQuery`not`不再适用于通用对象.请参阅https://github.com/jquery/jquery/issues/3147 (2认同)
  • 添加对~70k第三方库**的依赖并不是一个好主意**只是为了做到这一点,因为同样的事情可以在几行代码中完成,如此处的其他答案所示.但是,如果您已经在项目中使用jQuery,那么它将正常工作. (2认同)

Eri*_*ier 6

我会散列数组B,然后保持B中不存在的数组A中的值:

function getHash(array){
  // Hash an array into a set of properties
  //
  // params:
  //   array - (array) (!nil) the array to hash
  //
  // return: (object)
  //   hash object with one property set to true for each value in the array

  var hash = {};
  for (var i=0; i<array.length; i++){
    hash[ array[i] ] = true;
  }
  return hash;
}

function getDifference(a, b){
  // compute the difference a\b
  //
  // params:
  //   a - (array) (!nil) first array as a set of values (no duplicates)
  //   b - (array) (!nil) second array as a set of values (no duplicates)
  //
  // return: (array)
  //   the set of values (no duplicates) in array a and not in b, 
  //   listed in the same order as in array a.

  var hash = getHash(b);
  var diff = [];
  for (var i=0; i<a.length; i++){
    var value = a[i];
    if ( !hash[value]){
      diff.push(value);
    }
  }
  return diff;
}
Run Code Online (Sandbox Code Playgroud)


j-g*_*tus 5

结合 Christoph 的想法,并假设在数组和对象/哈希(each和朋友)上有几个非标准的迭代方法,我们可以在大约 20 行的线性时间内获得集差、并集和交集:

var setOPs = {
  minusAB : function (a, b) {
    var h = {};
    b.each(function (v) { h[v] = true; });
    return a.filter(function (v) { return !h.hasOwnProperty(v); });
  },
  unionAB : function (a, b) {
    var h = {}, f = function (v) { h[v] = true; };
    a.each(f);
    b.each(f);
    return myUtils.keys(h);
  },
  intersectAB : function (a, b) {
    var h = {};
    a.each(function (v) { h[v] = 1; });
    b.each(function (v) { h[v] = (h[v] || 0) + 1; });
    var fnSel = function (v, count) { return count > 1; };
    var fnVal = function (v, c) { return v; };
    return myUtils.select(h, fnSel, fnVal);
  }
};
Run Code Online (Sandbox Code Playgroud)

这假设eachfilter是为数组定义的,并且我们有两个实用程序方法:

  • myUtils.keys(hash): 返回一个包含哈希键的数组

  • myUtils.select(hash, fnSelector, fnEvaluator): 返回一个数组,其中包含调用返回 truefnEvaluator 的键/值对的 fnSelector结果。

select()松散的Common Lisp的启发,只是filter()map()集于一身。(最好将它们定义在 上Object.prototype,但这样做会对jQuery 造成严重破坏,所以我选择了静态实用程序方法。)

性能:测试

var a = [], b = [];
for (var i = 100000; i--; ) {
  if (i % 2 !== 0) a.push(i);
  if (i % 3 !== 0) b.push(i);
}
Run Code Online (Sandbox Code Playgroud)

给出两个包含 50,000 和 66,666 个元素的集合。使用这些值,AB 大约需要 75 毫秒,而联合和交集每个大约需要 150 毫秒。(Mac Safari 4.0,使用 Javascript Date 进行计时。)

我认为这对于 20 行代码来说是不错的回报。


chr*_*sen 5

使用Underscore.js(函数式 JS 库)

>>> var foo = [1,2,3]
>>> var bar = [1,2,4]
>>> _.difference(foo, bar);
[4]
Run Code Online (Sandbox Code Playgroud)


Bri*_*rns 5

一些简单的功能,借用@milan 的回答:

const setDifference = (a, b) => new Set([...a].filter(x => !b.has(x)));
const setIntersection = (a, b) => new Set([...a].filter(x => b.has(x)));
const setUnion = (a, b) => new Set([...a, ...b]);
Run Code Online (Sandbox Code Playgroud)

用法:

const a = new Set([1, 2]);
const b = new Set([2, 3]);

setDifference(a, b); // Set { 1 }
setIntersection(a, b); // Set { 2 }
setUnion(a, b); // Set { 1, 2, 3 }
Run Code Online (Sandbox Code Playgroud)


And*_*rtz 5

如果您使用Set的是s,它可以非常简单且高效:

function setDifference(a, b) {
  return new Set(Array.from(a).filter(item => !b.has(item)));
}
Run Code Online (Sandbox Code Playgroud)

由于Sets 在底层使用哈希函数*,因此该has函数比indexOf(如果您有超过 100 个项目,这很重要)要快得多。