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

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

笔记:

  • 壁虎专用的技巧是好的
  • 我更喜欢坚持原生function(但如果速度更快,我可以打开轻量级库)
  • 我见过,但没有testing, JS.Set (见前面的观点)

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

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

 A = [1, 2, 3, 4]; B = [1, 3, 4, 7]; diff = A.filter(function(x) { return B.indexOf(x) < 0 }) console.log(diff); 

好吧,7年后, ES6的Set对象很容易(但仍然不像python的A – B那么紧凑),据报道对于大型数组来说比indexOf更快:

 let a = new Set([1,2,3,4]); let b = new Set([5,4,3,2]); console.log(new Set([...a].filter(x => !b.has(x)))); //a\b => {1} console.log(new Set([...b].filter(x => !a.has(x)))); //b\a => {5} console.log(new Set([...a].filter(x => b.has(x)))); //a∩b => {2,3,4} 

您可以使用一个对象作为一个映射,以避免线性扫描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()方法用于获取唯一的属性名称; 如果所有元素都有唯一的string表示forms(如数字forms),则可以通过删除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> 

我会散列数组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; } 

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

这假定each filter都是为数组定义的,而且我们有两个实用方法:

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

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

select()松散地由Common Lisp启发,并且仅仅是filter()map()合并成一个。 (最好是在Object.prototype定义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元素。 有了这些值AB需要约75ms,而工会和交叉口约150ms每个。 (Mac Safari 4.0,使用Javascriptdate进行计时。)

我认为这是20行代码体面的回报。

使用Underscore.js (函数JS库)

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

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

 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; 

至于禁食的方式,这不是很优雅,但我已经运行了一些testing,以确保。 作为一个对象加载一个数组要大大快速地处理:

 var t, a, b, c, A; // 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(); A = {}; a.forEach(function(v) { A[v] = true; }); c = b.filter(function(v) { return !a[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 

但是,这只适用于string 。 如果您打算比较编号集,则需要将结果与parseInt进行映射。

你可以使用这个轻量级的array-diff开源组件。

例:

 diff([1,2,3], [1,2,3,4,5]) // => [4,5] 

它通过对传递的两个数组进行级联,并过滤包含的vals,返回一个表示两个数组之间差异的数组:

 function diff(firstArray: any[], secondArray: any[]): any[] { return firstArray.concat(secondArray).filter((val) => { return !(firstArray.includes(val) && secondArray.includes(val)); }); }; 

一些简单的函数,从@ 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 }