有没有办法返回JavaScript中两个数组之间的差?
例如:
var a1 = ['a', 'b'];
var a2 = ['a', 'b', 'c', 'd'];
// need ["c", "d"]
O(a1.length x log(a2.length))
-JavaScript可以实现这种性能吗?
有没有办法返回JavaScript中两个数组之间的差?
例如:
var a1 = ['a', 'b'];
var a2 = ['a', 'b', 'c', 'd'];
// need ["c", "d"]
O(a1.length x log(a2.length))
-JavaScript可以实现这种性能吗?
Answers:
我假设您正在比较普通数组。如果不是,则需要将for循环更改为for .. in循环。
function arr_diff (a1, a2) {
var a = [], diff = [];
for (var i = 0; i < a1.length; i++) {
a[a1[i]] = true;
}
for (var i = 0; i < a2.length; i++) {
if (a[a2[i]]) {
delete a[a2[i]];
} else {
a[a2[i]] = true;
}
}
for (var k in a) {
diff.push(k);
}
return diff;
}
console.log(arr_diff(['a', 'b'], ['a', 'b', 'c', 'd']));
console.log(arr_diff("abcd", "abcde"));
console.log(arr_diff("zxc", "zxc"));
如果您不关心向后兼容性,那么更好的解决方案是使用过滤器。但是,此解决方案仍然有效。
var a1 = ['a', 'b'];
和var a2 = ['a', 'b', 'c', 'd', 'b'];
,它将返回错误的答案,即['c', 'd', 'b']
代替['c', 'd']
。
function diff2(a, b) { var i, la = a.length, lb = b.length, res = []; if (!la) return b; else if (!lb) return a; for (i = 0; i < la; i++) { if (b.indexOf(a[i]) === -1) res.push(a[i]); } for (i = 0; i < lb; i++) { if (a.indexOf(b[i]) === -1) res.push(b[i]); } return res; }
使用ES7有更好的方法:
路口
let intersection = arr1.filter(x => arr2.includes(x));
因为[1,2,3] [2,3]
它会屈服[2,3]
。另一方面,for [1,2,3] [2,3,5]
将返回相同的内容。
区别
let difference = arr1.filter(x => !arr2.includes(x));
因为[1,2,3] [2,3]
它会屈服[1]
。另一方面,for [1,2,3] [2,3,5]
将返回相同的内容。
对于对称差异,您可以执行以下操作:
let difference = arr1
.filter(x => !arr2.includes(x))
.concat(arr2.filter(x => !arr1.includes(x)));
这样,您将得到一个包含arr1不在arr2中的所有元素的数组,反之亦然
正如@Joshaven Potter在他的答案中指出的那样,您可以将其添加到Array.prototype中,以便可以这样使用:
Array.prototype.diff = function(arr2) { return this.filter(x => !arr2.includes(x)); }
[1, 2, 3].diff([2, 3])
< 0
而不是== -1
Set
,值必须唯一,不是吗?
[1,2,3] [2,3,5]
鉴于数字是唯一的,这将是行得通的,但是如果您说了[1,1,2,3] [1,2,3,5]
并期望[1]
您不能使用Set
。但是,您的解决方案也不起作用:-//我最终创建了此功能,因为我想不出一种令人满意的方法来更简洁地实现它。如果您对此有任何想法,我很想知道!
Array.includes()
ES7功能,而无需ES6?(1) (2) -并继续使用ES6,您可以使用Array.some()
例如 let intersection = aArray.filter(a => bArray.some(b => a === b))
,否?
Array.prototype.diff = function(a) {
return this.filter(function(i) {return a.indexOf(i) < 0;});
};
////////////////////
// Examples
////////////////////
[1,2,3,4,5,6].diff( [3,4,5] );
// => [1, 2, 6]
["test1", "test2","test3","test4","test5","test6"].diff(["test1","test2","test3","test4"]);
// => ["test5", "test6"]
注意 indexOf和filter在ie9之前的ie中不可用。
[1,2,3].diff([3,4,5])
它将返回[1,2]
而不是返回,[1,2,4,5]
因此它不能解决原始问题中需要注意的问题。
到目前为止,这是使用jQuery精确获得所需结果的最简单方法:
var diff = $(old_array).not(new_array).get();
diff
现在包含的内容old_array
不在其中new_array
.not
与数组一起使用时,jQuery使用它的内置实用程序.grep()
专门用于过滤数组。我看不到这种变化。
Underscore(或它的替代产品Lo-Dash)中的差异方法也可以做到这一点:
(R)eturns the values from array that are not present in the other arrays
_.difference([1, 2, 3, 4, 5], [5, 2, 10]);
=> [1, 3, 4]
与任何Underscore函数一样,您也可以以更面向对象的方式使用它:
_([1, 2, 3, 4, 5]).difference([5, 2, 10]);
对于“差异”有两种可能的解释。我让你选择你想要的那个。说您有:
var a1 = ['a', 'b' ];
var a2 = [ 'b', 'c'];
如果要获取['a']
,请使用以下功能:
function difference(a1, a2) {
var result = [];
for (var i = 0; i < a1.length; i++) {
if (a2.indexOf(a1[i]) === -1) {
result.push(a1[i]);
}
}
return result;
}
如果你想获得['a', 'c']
(所有元素包含在任一 a1
或a2
,但不能同时-所谓的对称差),使用此功能:
function symmetricDifference(a1, a2) {
var result = [];
for (var i = 0; i < a1.length; i++) {
if (a2.indexOf(a1[i]) === -1) {
result.push(a1[i]);
}
}
for (i = 0; i < a2.length; i++) {
if (a1.indexOf(a2[i]) === -1) {
result.push(a2[i]);
}
}
return result;
}
如果使用lodash,则可以使用_.difference(a1, a2)
(上述情况1)或_.xor(a1, a2)
(情况2)。
如果使用的是Underscore.js,则可以在_.difference(a1, a2)
案例1中使用该函数。
上面的代码在所有浏览器上均适用。但是,对于包含约10,000项以上的大型阵列,它变得非常慢,因为它具有O(n²)复杂度。在许多现代浏览器上,我们可以利用ES6 Set
对象来加快处理速度。Lodash Set
会在可用时自动使用。如果您不使用lodash,请使用以下实现,灵感来自Axel Rauschmayer的博客文章:
function difference(a1, a2) {
var a2Set = new Set(a2);
return a1.filter(function(x) { return !a2Set.has(x); });
}
function symmetricDifference(a1, a2) {
return difference(a1, a2).concat(difference(a2, a1));
}
如果您关心-0,+ 0,NaN或稀疏数组,则所有示例的行为可能令人惊讶或不明显。(对于大多数用途,这无关紧要。)
要获得对称差异,您需要以两种方式(或在有多个阵列的情况下以各种方式)比较阵列
// diff between just two arrays:
function arrayDiff(a, b) {
return [
...a.filter(x => !b.includes(x)),
...b.filter(x => !a.includes(x))
];
}
// diff between multiple arrays:
function arrayDiff(...arrays) {
return [].concat(...arrays.map( (arr, i) => {
const others = arrays.slice(0);
others.splice(i, 1);
const unique = [...new Set([].concat(...others))];
return arr.filter(x => !unique.includes(x));
}));
}
// diff between just two arrays:
function arrayDiff(a, b) {
return [
...a.filter(x => b.indexOf(x) === -1),
...b.filter(x => a.indexOf(x) === -1)
];
}
// diff between multiple arrays:
function arrayDiff(...arrays) {
return [].concat(...arrays.map( (arr, i) => {
const others = arrays.slice(0);
others.splice(i, 1);
const unique = [...new Set([].concat(...others))];
return arr.filter(x => unique.indexOf(x) === -1);
}));
}
// diff between just two arrays:
function arrayDiff(a, b) {
var arrays = Array.prototype.slice.call(arguments);
var diff = [];
arrays.forEach(function(arr, i) {
var other = i === 1 ? a : b;
arr.forEach(function(x) {
if (other.indexOf(x) === -1) {
diff.push(x);
}
});
})
return diff;
}
// diff between multiple arrays:
function arrayDiff() {
var arrays = Array.prototype.slice.call(arguments);
var diff = [];
arrays.forEach(function(arr, i) {
var others = arrays.slice(0);
others.splice(i, 1);
var otherValues = Array.prototype.concat.apply([], others);
var unique = otherValues.filter(function (x, j) {
return otherValues.indexOf(x) === j;
});
diff = diff.concat(arr.filter(x => unique.indexOf(x) === -1));
});
return diff;
}
例:
// diff between two arrays:
const a = ['a', 'd', 'e'];
const b = ['a', 'b', 'c', 'd'];
arrayDiff(a, b); // (3) ["e", "b", "c"]
// diff between multiple arrays
const a = ['b', 'c', 'd', 'e', 'g'];
const b = ['a', 'b'];
const c = ['a', 'e', 'f'];
arrayDiff(a, b, c); // (4) ["c", "d", "g", "f"]
function arrayDiffByKey(key, ...arrays) {
return [].concat(...arrays.map( (arr, i) => {
const others = arrays.slice(0);
others.splice(i, 1);
const unique = [...new Set([].concat(...others))];
return arr.filter( x =>
!unique.some(y => x[key] === y[key])
);
}));
}
例:
const a = [{k:1}, {k:2}, {k:3}];
const b = [{k:1}, {k:4}, {k:5}, {k:6}];
const c = [{k:3}, {k:5}, {k:7}];
arrayDiffByKey('k', a, b, c); // (4) [{k:2}, {k:4}, {k:6}, {k:7}]
以下解决方案是ES6中一种更清洁的方法。
var a1 = ['a', 'b'];
var a2 = ['a', 'b', 'c', 'd'];
a2.filter(d => !a1.includes(d)) // gives ["c", "d"]
a2.filter(d => a1.includes(d)) // gives ["a", "b"]
[ ...a2.filter(d => !a1.includes(d)),
...a1.filter(d => !a2.includes(d)) ]
a1 = ['a', 'b', 'e']
:e将不会被提取。
在这种情况下,您可以使用Set。针对此类操作(联合,相交,差)进行了优化。
一旦不允许重复,请确保适用于您的情况。
var a = new JS.Set([1,2,3,4,5,6,7,8,9]);
var b = new JS.Set([2,4,6,8]);
a.difference(b)
// -> Set{1,3,5,7,9}
Set
功能而不必获得其他所有功能真是太可惜了
function diff(a1, a2) {
return a1.concat(a2).filter(function(val, index, arr){
return arr.indexOf(val) === arr.lastIndexOf(val);
});
}
合并两个数组,唯一值将仅出现一次,因此indexOf()将与lastIndexOf()相同。
要从另一个数组中减去一个数组,只需使用以下代码段:
var a1 = ['1','2','3','4','6'];
var a2 = ['3','4','5'];
var items = new Array();
items = jQuery.grep(a1,function (item) {
return jQuery.inArray(item, a2) < 0;
});
它将返回['1,'2','6'],它们是第一个数组中不存在于第二个数组中的项。
因此,根据您的问题样本,以下代码是准确的解决方案:
var array1 = ["test1", "test2","test3", "test4"];
var array2 = ["test1", "test2","test3","test4", "test5", "test6"];
var _array = new Array();
_array = jQuery.grep(array2, function (item) {
return jQuery.inArray(item, array1) < 0;
});
随着带有set和splat运算符的ES6的到来(在仅在Firefox中可用时,请检查兼容性表),您可以编写以下一行:
var a = ['a', 'b', 'c', 'd'];
var b = ['a', 'b'];
var b1 = new Set(b);
var difference = [...new Set([...a].filter(x => !b1.has(x)))];
这将导致[ "c", "d" ]
。
b.filter(x => !a.indexOf(x)))
O(n + m)
您的解决方案是O(n * m)
n和m是数组的长度。列出长长的清单,我的解决方案将在几秒钟内运行,而您的解决方案将花费数小时。
a.filter(x => !b1.has(x))
更简单。请注意,规范仅要求复杂度平均n * f(m) + m
为f(m)
亚线性。它比n * m
(但不一定)更好n + m
。
var difference = [...new Set([...a].filter(x => !b1.has(x)))];
为什么要创建重复的'a'数组?为什么要将过滤器的结果转换为集合,然后又转换为数组?这不是等同于var difference = a.filter(x => !b1.has(x));
计算difference
两个数组之间的和是Set
操作之一。该术语已经表明Set
应该使用本机类型,以提高查找速度。无论如何,当您计算两组之间的差异时,存在三个排列:
[+left difference] [-intersection] [-right difference]
[-left difference] [-intersection] [+right difference]
[+left difference] [-intersection] [+right difference]
这是一个反映这些排列的功能解决方案。
difference
:// small, reusable auxiliary functions
const apply = f => x => f(x);
const flip = f => y => x => f(x) (y);
const createSet = xs => new Set(xs);
const filter = f => xs => xs.filter(apply(f));
// left difference
const differencel = xs => ys => {
const zs = createSet(ys);
return filter(x => zs.has(x)
? false
: true
) (xs);
};
// mock data
const xs = [1,2,2,3,4,5];
const ys = [0,1,2,3,3,3,6,7,8,9];
// run the computation
console.log( differencel(xs) (ys) );
difference
:differencer
是微不足道的。它只是differencel
带有翻转的参数。为了方便起见,您可以编写一个函数:const differencer = flip(differencel)
。就这样!
difference
:现在我们有了左右一个,实现对称也difference
变得很简单:
// small, reusable auxiliary functions
const apply = f => x => f(x);
const flip = f => y => x => f(x) (y);
const concat = y => xs => xs.concat(y);
const createSet = xs => new Set(xs);
const filter = f => xs => xs.filter(apply(f));
// left difference
const differencel = xs => ys => {
const zs = createSet(ys);
return filter(x => zs.has(x)
? false
: true
) (xs);
};
// symmetric difference
const difference = ys => xs =>
concat(differencel(xs) (ys)) (flip(differencel) (xs) (ys));
// mock data
const xs = [1,2,2,3,4,5];
const ys = [0,1,2,3,3,3,6,7,8,9];
// run the computation
console.log( difference(xs) (ys) );
我想这个例子是一个很好的起点,可以使人对函数式编程的含义有一个印象:
使用可以以许多不同方式插入的构件块进行编程。
indexOf()
对于小型阵列,使用的解决方案是可以的,但是随着阵列长度的增加,算法的性能也会越来越高O(n^2)
。这是一个解决方案,通过使用对象作为关联数组将数组项存储为键,将对大型数组有更好的性能。它还自动消除重复项,但仅适用于字符串值(或可以安全存储为字符串的值):
function arrayDiff(a1, a2) {
var o1={}, o2={}, diff=[], i, len, k;
for (i=0, len=a1.length; i<len; i++) { o1[a1[i]] = true; }
for (i=0, len=a2.length; i<len; i++) { o2[a2[i]] = true; }
for (k in o1) { if (!(k in o2)) { diff.push(k); } }
for (k in o2) { if (!(k in o1)) { diff.push(k); } }
return diff;
}
var a1 = ['a', 'b'];
var a2 = ['a', 'b', 'c', 'd'];
arrayDiff(a1, a2); // => ['c', 'd']
arrayDiff(a2, a1); // => ['c', 'd']
Joshaven Potter的上述回答很棒。但是它返回的数组B中的元素不在数组C中,反之亦然。例如,如果var a=[1,2,3,4,5,6].diff( [3,4,5,7]);
这样,它将输出:==> [1,2,6]
,但不会 输出[1,2,6,7]
,这是两者之间的实际差异。您仍然可以使用上面的Potter的代码,但是也可以一次向后重做比较:
Array.prototype.diff = function(a) {
return this.filter(function(i) {return !(a.indexOf(i) > -1);});
};
////////////////////
// Examples
////////////////////
var a=[1,2,3,4,5,6].diff( [3,4,5,7]);
var b=[3,4,5,7].diff([1,2,3,4,5,6]);
var c=a.concat(b);
console.log(c);
这应该输出: [ 1, 2, 6, 7 ]
解决问题的另一种方法
function diffArray(arr1, arr2) {
return arr1.concat(arr2).filter(function (val) {
if (!(arr1.includes(val) && arr2.includes(val)))
return val;
});
}
diffArray([1, 2, 3, 7], [3, 2, 1, 4, 5]); // return [7, 4, 5]
另外,您可以使用箭头函数语法:
const diffArray = (arr1, arr2) => arr1.concat(arr2)
.filter(val => !(arr1.includes(val) && arr2.includes(val)));
diffArray([1, 2, 3, 7], [3, 2, 1, 4, 5]); // return [7, 4, 5]
具有JavaScript过滤功能的非常简单的解决方案:
var a1 = ['a', 'b'];
var a2 = ['a', 'b', 'c', 'd'];
function diffArray(arr1, arr2) {
var newArr = [];
var myArr = arr1.concat(arr2);
newArr = myArr.filter(function(item){
return arr2.indexOf(item) < 0 || arr1.indexOf(item) < 0;
});
alert(newArr);
}
diffArray(a1, a2);
这个怎么样:
Array.prototype.contains = function(needle){
for (var i=0; i<this.length; i++)
if (this[i] == needle) return true;
return false;
}
Array.prototype.diff = function(compare) {
return this.filter(function(elem) {return !compare.contains(elem);})
}
var a = new Array(1,4,7, 9);
var b = new Array(4, 8, 7);
alert(a.diff(b));
因此,您可以通过这种方式array1.diff(array2)
来获得它们的差异(尽管我相信算法的时间复杂度很差-我相信O(array1.length x array2.length))
使用http://phrogz.net/JS/ArraySetMath.js,您可以:
var array1 = ["test1", "test2","test3", "test4"];
var array2 = ["test1", "test2","test3","test4", "test5", "test6"];
var array3 = array2.subtract( array1 );
// ["test5", "test6"]
var array4 = array1.exclusion( array2 );
// ["test5", "test6"]
filter
)fn
回调参数,可让您指定如何比较数组项function diff(a, b, fn){
var max = Math.max(a.length, b.length);
d = [];
fn = typeof fn === 'function' ? fn : false
for(var i=0; i < max; i++){
var ac = i < a.length ? a[i] : undefined
bc = i < b.length ? b[i] : undefined;
for(var k=0; k < max; k++){
ac = ac === undefined || (k < b.length && (fn ? fn(ac, b[k]) : ac == b[k])) ? undefined : ac;
bc = bc === undefined || (k < a.length && (fn ? fn(bc, a[k]) : bc == a[k])) ? undefined : bc;
if(ac == undefined && bc == undefined) break;
}
ac !== undefined && d.push(ac);
bc !== undefined && d.push(bc);
}
return d;
}
alert(
"Test 1: " +
diff(
[1, 2, 3, 4],
[1, 4, 5, 6, 7]
).join(', ') +
"\nTest 2: " +
diff(
[{id:'a',toString:function(){return this.id}},{id:'b',toString:function(){return this.id}},{id:'c',toString:function(){return this.id}},{id:'d',toString:function(){return this.id}}],
[{id:'a',toString:function(){return this.id}},{id:'e',toString:function(){return this.id}},{id:'f',toString:function(){return this.id}},{id:'d',toString:function(){return this.id}}],
function(a, b){ return a.id == b.id; }
).join(', ')
);
length
值。它已经是普通财产了。jsperf.com/array-length-caching
只是想着...为了一个挑战;-)会起作用...(对于字符串,数字等的基本数组)没有嵌套数组
function diffArrays(arr1, arr2, returnUnion){
var ret = [];
var test = {};
var bigArray, smallArray, key;
if(arr1.length >= arr2.length){
bigArray = arr1;
smallArray = arr2;
} else {
bigArray = arr2;
smallArray = arr1;
}
for(var i=0;i<bigArray.length;i++){
key = bigArray[i];
test[key] = true;
}
if(!returnUnion){
//diffing
for(var i=0;i<smallArray.length;i++){
key = smallArray[i];
if(!test[key]){
test[key] = null;
}
}
} else {
//union
for(var i=0;i<smallArray.length;i++){
key = smallArray[i];
if(!test[key]){
test[key] = true;
}
}
}
for(var i in test){
ret.push(i);
}
return ret;
}
array1 = "test1", "test2","test3", "test4", "test7"
array2 = "test1", "test2","test3","test4", "test5", "test6"
diffArray = diffArrays(array1, array2);
//returns ["test5","test6","test7"]
diffArray = diffArrays(array1, array2, true);
//returns ["test1", "test2","test3","test4", "test5", "test6","test7"]
请注意,排序可能不会如上所述...但是如果需要,请在数组上调用.sort()对其进行排序。
我想要一个类似的函数,它接受一个旧数组和一个新数组,并给我一个添加项数组和一个删除项数组,并且我希望它高效(因此没有.contains!)。
您可以在这里使用我建议的解决方案:http : //jsbin.com/osewu3/12。
谁能看到该算法的任何问题/改进?谢谢!
代码清单:
function diff(o, n) {
// deal with empty lists
if (o == undefined) o = [];
if (n == undefined) n = [];
// sort both arrays (or this won't work)
o.sort(); n.sort();
// don't compare if either list is empty
if (o.length == 0 || n.length == 0) return {added: n, removed: o};
// declare temporary variables
var op = 0; var np = 0;
var a = []; var r = [];
// compare arrays and add to add or remove lists
while (op < o.length && np < n.length) {
if (o[op] < n[np]) {
// push to diff?
r.push(o[op]);
op++;
}
else if (o[op] > n[np]) {
// push to diff?
a.push(n[np]);
np++;
}
else {
op++;np++;
}
}
// add remaining items
if( np < n.length )
a = a.concat(n.slice(np, n.length));
if( op < o.length )
r = r.concat(o.slice(op, o.length));
return {added: a, removed: r};
}
我一直在寻找一个简单的答案,该答案不涉及使用不同的库,我想出了自己的答案,我认为这里没有提到。我不知道它有多有效,但它能起作用。
function find_diff(arr1, arr2) {
diff = [];
joined = arr1.concat(arr2);
for( i = 0; i <= joined.length; i++ ) {
current = joined[i];
if( joined.indexOf(current) == joined.lastIndexOf(current) ) {
diff.push(current);
}
}
return diff;
}
对于我的代码,我也需要取出重复项,但是我猜这并不总是首选。
我猜主要的缺点是它可能会比较许多已经被拒绝的选项。
littlebit修复最佳答案
function arr_diff(a1, a2)
{
var a=[], diff=[];
for(var i=0;i<a1.length;i++)
a[a1[i]]=a1[i];
for(var i=0;i<a2.length;i++)
if(a[a2[i]]) delete a[a2[i]];
else a[a2[i]]=a2[i];
for(var k in a)
diff.push(a[k]);
return diff;
}
这将考虑当前元素的类型。b / c当我们制作a [a1 [i]]时,它将其原始值转换为字符串值,因此我们丢失了实际值。