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

时间:2009-11-12 15:42:40

标签: javascript arrays set-difference

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

注意:

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

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

12 个答案:

答案 0 :(得分:149)

如果不知道这是否最有效,但可能是最短的

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);

答案 1 :(得分:57)

嗯,7年后,ES6's Set对象很容易(但仍然不像蟒蛇A-B那么紧凑),据报道,对于大型阵列,速度比if (view.isRenderingPhase()) {...}快: / p>

&#13;
&#13;
indexOf
&#13;
&#13;
&#13;

答案 2 :(得分:15)

您可以将对象用作地图,以避免为BA的每个元素线性扫描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()

非标准user187291's answer用于获取唯一的属性名称;如果所有元素都具有唯一的字符串表示形式(如数字的情况),则可以通过删除{{1}}调用来加速代码。

答案 3 :(得分:9)

使用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>

答案 4 :(得分:6)

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

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;
}

答案 5 :(得分:4)

结合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);
  }
};

这假定为数组定义了eachfilter,并且我们有两种实用方法:

  • myUtils.keys(hash):返回一个 带有散列键的数组

  • myUtils.select(hash, fnSelector, fnEvaluator):返回一个数组 调用fnEvaluator的结果 在键/值对上 fnSelector返回true。

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);
}

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

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

答案 6 :(得分:4)

如果您使用 Set,它可以非常简单和高效:

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

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

答案 7 :(得分:3)

使用Underscore.js(功能JS库)

>>> var foo = [1,2,3]
>>> var bar = [1,2,4]
>>> _.difference(foo, bar);
[4]

答案 8 :(得分:2)

至于禁食方式,这不是那么优雅,但我已经进行了一些测试以确定。将一个数组作为对象加载要大量处理的速度要快得多:

var t, a, b, c, objA;

    // Fill some arrays to compare
a = Array(30000).fill(0).map(function(v,i) {
    return i.toFixed();
});
b = Array(20000).fill(0).map(function(v,i) {
    return (i*2).toFixed();
});

    // Simple indexOf inside filter
t = Date.now();
c = b.filter(function(v) { return a.indexOf(v) < 0; });
console.log('completed indexOf in %j ms with result %j length', Date.now() - t, c.length);

    // Load `a` as Object `A` first to avoid indexOf in filter
t = Date.now();
objA = {};
a.forEach(function(v) { objA[v] = true; });
c = b.filter(function(v) { return !objA[v]; });
console.log('completed Object in %j ms with result %j length', Date.now() - t, c.length);

结果:

completed indexOf in 1219 ms with result 5000 length
completed Object in 8 ms with result 5000 length

但是,这仅适用于字符串。如果您打算比较编号集,则需要使用 parseFloat 映射结果。

答案 9 :(得分:2)

一些简单的功能,借鉴@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]);

用法:

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 }

答案 10 :(得分:1)

这有效,但我认为另一个更短,更优雅

A = [1, 'a', 'b', 12];
B = ['a', 3, 4, 'b'];

diff_set = {
    ar : {},
    diff : Array(),
    remove_set : function(a) { ar = a; return this; },
    remove: function (el) {
        if(ar.indexOf(el)<0) this.diff.push(el);
    }
}

A.forEach(diff_set.remove_set(B).remove,diff_set);
C = diff_set.diff;

答案 11 :(得分:1)

看看这些解决方案中的哪一个,它们在小情况下还是不错的。但是,当您将它们炸毁一百万个项目时,时间复杂性开始变得愚蠢。

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

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

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

  for (const v of B) {
    if (!set.delete(v)) {
        yield v;
    }
  }

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

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

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

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