如何在 JavaScript 中获得两个对象数组之间的差异

我有两个这样的结果集:

// Result 1
[
{ value: "0", display: "Jamsheer" },
{ value: "1", display: "Muhammed" },
{ value: "2", display: "Ravi" },
{ value: "3", display: "Ajmal" },
{ value: "4", display: "Ryan" }
]


// Result 2
[
{ value: "0", display: "Jamsheer" },
{ value: "1", display: "Muhammed" },
{ value: "2", display: "Ravi" },
{ value: "3", display: "Ajmal" },
]

我需要的最终结果是这些数组之间的差异——最终结果应该是这样的:

[{ value: "4", display: "Ryan" }]

有可能在 JavaScript 中做类似的事情吗?

325886 次浏览

只使用本地 JS,这样的东西可以工作:

const a = [{ value:"0", display:"Jamsheer" }, { value:"1", display:"Muhammed" }, { value:"2", display:"Ravi" }, { value:"3", display:"Ajmal" }, { value:"4", display:"Ryan" }];
const b = [{ value:"0", display:"Jamsheer", $$hashKey:"008" }, { value:"1", display:"Muhammed", $$hashKey:"009" }, { value:"2", display:"Ravi", $$hashKey:"00A" }, { value:"3", display:"Ajmal", $$hashKey:"00B" }];


// A comparer used to determine if two entries are equal.
const isSameUser = (a, b) => a.value === b.value && a.display === b.display;


// Get items that only occur in the left array,
// using the compareFunction to determine equality.
const onlyInLeft = (left, right, compareFunction) =>
left.filter(leftValue =>
!right.some(rightValue =>
compareFunction(leftValue, rightValue)));


const onlyInA = onlyInLeft(a, b, isSameUser);
const onlyInB = onlyInLeft(b, a, isSameUser);


const result = [...onlyInA, ...onlyInB];


console.log(result);

你可以结合使用 Array.prototype.filter()Array.prototype.some()

下面是一个例子(假设数组存储在变量 result1result2中) :

//Find values that are in result1 but not in result2
var uniqueResultOne = result1.filter(function(obj) {
return !result2.some(function(obj2) {
return obj.value == obj2.value;
});
});


//Find values that are in result2 but not in result1
var uniqueResultTwo = result2.filter(function(obj) {
return !result1.some(function(obj2) {
return obj.value == obj2.value;
});
});


//Combine the two arrays of unique entries
var result = uniqueResultOne.concat(uniqueResultTwo);

我采取了一种稍微更通用的方法,尽管在思想上与 @ Cerbrus < a href = “ https://stackoverflow. com/users/1835379/cerbrus”>@Cerbrus @ Kasper Moerch 的方法相似。我创建了一个函数,它接受一个谓词来确定两个对象是否相等(这里我们忽略了 $$hashKey属性,但它可以是任何东西) ,然后返回一个函数,根据这个谓词计算两个列表的对称差:

a = [{ value:"4a55eff3-1e0d-4a81-9105-3ddd7521d642", display:"Jamsheer"}, { value:"644838b3-604d-4899-8b78-09e4799f586f", display:"Muhammed"}, { value:"b6ee537a-375c-45bd-b9d4-4dd84a75041d", display:"Ravi"}, { value:"e97339e1-939d-47ab-974c-1b68c9cfb536", display:"Ajmal"},  { value:"a63a6f77-c637-454e-abf2-dfb9b543af6c", display:"Ryan"}]
b = [{ value:"4a55eff3-1e0d-4a81-9105-3ddd7521d642", display:"Jamsheer", $$hashKey:"008"}, { value:"644838b3-604d-4899-8b78-09e4799f586f", display:"Muhammed", $$hashKey:"009"}, { value:"b6ee537a-375c-45bd-b9d4-4dd84a75041d", display:"Ravi", $$hashKey:"00A"}, { value:"e97339e1-939d-47ab-974c-1b68c9cfb536", display:"Ajmal", $$hashKey:"00B"}]


var makeSymmDiffFunc = (function() {
var contains = function(pred, a, list) {
var idx = -1, len = list.length;
while (++idx < len) {if (pred(a, list[idx])) {return true;}}
return false;
};
var complement = function(pred, a, b) {
return a.filter(function(elem) {return !contains(pred, elem, b);});
};
return function(pred) {
return function(a, b) {
return complement(pred, a, b).concat(complement(pred, b, a));
};
};
}());


var myDiff = makeSymmDiffFunc(function(x, y) {
return x.value === y.value && x.display === y.display;
});


var result = myDiff(a, b); //=>  {value="a63a6f77-c637-454e-abf2-dfb9b543af6c", display="Ryan"}

相对于 Cerebrus 的方法(正如 Kasper Moerch 的方法一样) ,它有一个小的优势,那就是它可以提前逃脱; 如果它找到了匹配,它就不用费心检查列表的其余部分。如果我有一个方便的 curry函数,我会做一些不同的事情,但是这个工作得很好。

解释

一条评论要求对初学者进行更详细的解释。

我们将以下函数传递给 makeSymmDiffFunc:

function(x, y) {
return x.value === y.value && x.display === y.display;
}

这个函数就是我们判断两个对象是否相等的方法。像所有返回 truefalse的函数一样,它可以被称为“谓词函数”,但这只是术语。主要的一点是,makeSymmDiffFunc配置了一个函数,该函数接受两个对象,如果我们认为它们是相等的,则返回 true,如果我们不认为它们是相等的,则返回 false

使用这个函数,makeSymmDiffFunc(读作“ make 对称差函数”)返回一个新函数:

        return function(a, b) {
return complement(pred, a, b).concat(complement(pred, b, a));
};

这是我们实际使用的函数。我们传递给它两个列表,它会在第一个列表中找到元素,而不是在第二个列表中,然后在第二个列表中找到元素,而不是在第一个列表中,然后将这两个列表合并起来。

不过,再看一遍,我完全可以从您的代码中得到启发,并通过使用 some大大简化 main 函数:

var makeSymmDiffFunc = (function() {
var complement = function(pred, a, b) {
return a.filter(function(x) {
return !b.some(function(y) {return pred(x, y);});
});
};
return function(pred) {
return function(a, b) {
return complement(pred, a, b).concat(complement(pred, b, a));
};
};
}());

complement使用谓词并返回其第一个列表中的元素,而不是第二个列表中的元素。这比我第一次使用单独的 contains函数更简单。

最后,将 main 函数包装在一个立即调用的函数表达式(翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳)中,以将内部 complement函数保持在全局范围之外。


更新,几年后

既然 ES2015已经变得相当普遍,我建议使用同样的技术,而不是那些繁文缛节:

const diffBy = (pred) => (a, b) => a.filter(x => !b.some(y => pred(x, y)))
const makeSymmDiffFunc = (pred) => (a, b) => diffBy(pred)(a, b).concat(diffBy(pred)(b, a))


const myDiff = makeSymmDiffFunc((x, y) => x.value === y.value && x.display === y.display)


const result = myDiff(a, b)
//=>  {value="a63a6f77-c637-454e-abf2-dfb9b543af6c", display="Ryan"}

我已经做了一个通用的差异,比较2个对象的任何类型,可以运行一个修改处理程序 Gist.github.com/bortunac“ diff.js” 使用的前提:

old_obj={a:1,b:2,c:[1,2]}
now_obj={a:2 , c:[1,3,5],d:55}

所以属性 a 被修改,b 被删除,c 被修改,d 被添加

var handler=function(type,pointer){
console.log(type,pointer,this.old.point(pointer)," | ",this.now.point(pointer));

}

现在用

df=new diff();
df.analize(now_obj,old_obj);
df.react(handler);

控制台会显示

mdf ["a"]  1 | 2
mdf ["c", "1"]  2 | 3
add ["c", "2"]  undefined | 5
add ["d"]  undefined | 55
del ["b"]  2 | undefined

如果您愿意使用外部库,可以使用 _。为了实现这一点,在 underscore.js 中使用了不同的。我不知道。差异返回数组中其他数组中不存在的值。

_.difference([1,2,3,4,5][1,4,10])


==>[2,3,5]

我认为“ Cerbrus 解决方案”是正确的。我实现了相同的解决方案,但是将重复的代码提取到它自己的函数(DRY)中。

 function filterByDifference(array1, array2, compareField) {
var onlyInA = differenceInFirstArray(array1, array2, compareField);
var onlyInb = differenceInFirstArray(array2, array1, compareField);
return onlyInA.concat(onlyInb);
}


function differenceInFirstArray(array1, array2, compareField) {
return array1.filter(function (current) {
return array2.filter(function (current_b) {
return current_b[compareField] === current[compareField];
}).length == 0;
});
}
import differenceBy from 'lodash/differenceBy'


const myDifferences = differenceBy(Result1, Result2, 'value')

这将返回两个对象数组之间的差异,使用键 value来比较它们。请注意,具有相同值的两个值将不会返回,因为其他键将被忽略。

这是 浪荡的一部分。

您可以创建一个对象,将键作为数组中每个对象对应的唯一值,然后根据其他对象中键的存在对每个数组进行筛选。它降低了操作的复杂性。

ES6

let a = [{ value:"4a55eff3-1e0d-4a81-9105-3ddd7521d642", display:"Jamsheer"}, { value:"644838b3-604d-4899-8b78-09e4799f586f", display:"Muhammed"}, { value:"b6ee537a-375c-45bd-b9d4-4dd84a75041d", display:"Ravi"}, { value:"e97339e1-939d-47ab-974c-1b68c9cfb536", display:"Ajmal"},  { value:"a63a6f77-c637-454e-abf2-dfb9b543af6c", display:"Ryan"}];
let b = [{ value:"4a55eff3-1e0d-4a81-9105-3ddd7521d642", display:"Jamsheer", $$hashKey:"008"}, { value:"644838b3-604d-4899-8b78-09e4799f586f", display:"Muhammed", $$hashKey:"009"}, { value:"b6ee537a-375c-45bd-b9d4-4dd84a75041d", display:"Ravi", $$hashKey:"00A"}, { value:"e97339e1-939d-47ab-974c-1b68c9cfb536", display:"Ajmal", $$hashKey:"00B"}];


let valuesA = a.reduce((a,{value}) => Object.assign(a, {[value]:value}), {});
let valuesB = b.reduce((a,{value}) => Object.assign(a, {[value]:value}), {});
let result = [...a.filter(({value}) => !valuesB[value]), ...b.filter(({value}) => !valuesA[value])];
console.log(result);

ES5

var a = [{ value:"4a55eff3-1e0d-4a81-9105-3ddd7521d642", display:"Jamsheer"}, { value:"644838b3-604d-4899-8b78-09e4799f586f", display:"Muhammed"}, { value:"b6ee537a-375c-45bd-b9d4-4dd84a75041d", display:"Ravi"}, { value:"e97339e1-939d-47ab-974c-1b68c9cfb536", display:"Ajmal"},  { value:"a63a6f77-c637-454e-abf2-dfb9b543af6c", display:"Ryan"}];
var b = [{ value:"4a55eff3-1e0d-4a81-9105-3ddd7521d642", display:"Jamsheer", $$hashKey:"008"}, { value:"644838b3-604d-4899-8b78-09e4799f586f", display:"Muhammed", $$hashKey:"009"}, { value:"b6ee537a-375c-45bd-b9d4-4dd84a75041d", display:"Ravi", $$hashKey:"00A"}, { value:"e97339e1-939d-47ab-974c-1b68c9cfb536", display:"Ajmal", $$hashKey:"00B"}];


var valuesA = a.reduce(function(a,c){a[c.value] = c.value; return a; }, {});
var valuesB = b.reduce(function(a,c){a[c.value] = c.value; return a; }, {});
var result = a.filter(function(c){ return !valuesB[c.value]}).concat(b.filter(function(c){ return !valuesA[c.value]}));
console.log(result);

我发现这个解决方案使用过滤器和一些。

resultFilter = (firstArray, secondArray) => {
return firstArray.filter(firstArrayItem =>
!secondArray.some(
secondArrayItem => firstArrayItem._user === secondArrayItem._user
)
);
};

这里的大多数答案都相当复杂,但这背后的逻辑不是很简单吗?

  1. 检查哪个数组更长,并提供它作为第一个参数(如果长度相等,参数顺序不重要)
  2. 遍历数组1。
  3. 对于 array1的当前迭代元素,检查它是否存在于 array2中
  4. 如果它不存在,则
  5. 把它推到“差异”数组
const getArraysDifference = (longerArray, array2) => {
const difference = [];


longerArray.forEach(el1 => {      /*1*/
el1IsPresentInArr2 = array2.some(el2 => el2.value === el1.value); /*2*/


if (!el1IsPresentInArr2) { /*3*/
difference.push(el1);    /*4*/
}
});


return difference;
}

O (n ^ 2)复杂度。

对于那些喜欢 ES6中的一行程序解决方案的人,可以这样做:

const arrayOne = [
{ value: "4a55eff3-1e0d-4a81-9105-3ddd7521d642", display: "Jamsheer" },
{ value: "644838b3-604d-4899-8b78-09e4799f586f", display: "Muhammed" },
{ value: "b6ee537a-375c-45bd-b9d4-4dd84a75041d", display: "Ravi" },
{ value: "e97339e1-939d-47ab-974c-1b68c9cfb536", display: "Ajmal" },
{ value: "a63a6f77-c637-454e-abf2-dfb9b543af6c", display: "Ryan" },
];
          

const arrayTwo = [
{ value: "4a55eff3-1e0d-4a81-9105-3ddd7521d642", display: "Jamsheer"},
{ value: "644838b3-604d-4899-8b78-09e4799f586f", display: "Muhammed"},
{ value: "b6ee537a-375c-45bd-b9d4-4dd84a75041d", display: "Ravi"},
{ value: "e97339e1-939d-47ab-974c-1b68c9cfb536", display: "Ajmal"},
];


const results = arrayOne.filter(({ value: id1 }) => !arrayTwo.some(({ value: id2 }) => id2 === id1));


console.log(results);

最通用和简单的方法:

findObject(listOfObjects, objectToSearch) {
let found = false, matchingKeys = 0;
for(let object of listOfObjects) {
found = false;
matchingKeys = 0;
for(let key of Object.keys(object)) {
if(object[key]==objectToSearch[key]) matchingKeys++;
}
if(matchingKeys==Object.keys(object).length) {
found = true;
break;
}
}
return found;
}


get_removed_list_of_objects(old_array, new_array) {
// console.log('old:',old_array);
// console.log('new:',new_array);
let foundList = [];
for(let object of old_array) {
if(!this.findObject(new_array, object)) foundList.push(object);
}
return foundList;
}


get_added_list_of_objects(old_array, new_array) {
let foundList = [];
for(let object of new_array) {
if(!this.findObject(old_array, object)) foundList.push(object);
}
return foundList;
}

当涉及到大数组时,我更喜欢 map 对象。

// create tow arrays
array1 = Array.from({length: 400},() => ({value:Math.floor(Math.random() * 4000)}))
array2 = Array.from({length: 400},() => ({value:Math.floor(Math.random() * 4000)}))


// calc diff with some function
console.time('diff with some');
results = array2.filter(({ value: id1 }) => array1.some(({ value: id2 }) => id2 === id1));
console.log('diff results ',results.length)
console.timeEnd('diff with some');


// calc diff with map object
console.time('diff with map');
array1Map = {};
for(const item1 of array1){
array1Map[item1.value] = true;
}
results = array2.filter(({ value: id2 }) => array1Map[id2]);
console.log('map results ',results.length)
console.timeEnd('diff with map');

JavaScript 有 Maps,提供 O (1)插入和查找时间。因此,这个问题可以用 O (n)来解决(而不是像其他所有答案那样用 O (n2)来解决)。为此,有必要为每个对象生成唯一的原语(字符串/数字)键。可以使用 JSON.stringify,但是这很容易出错,因为元素的顺序可能会影响相等性:

 JSON.stringify({ a: 1, b: 2 }) !== JSON.stringify({ b: 2, a: 1 })

因此,我将使用一个不出现在任何值中的分隔符,并手动组成一个字符串:

const toHash = value => value.value + "@" + value.display;

然后创建一个 Map。当一个元素已经存在于 Map 中时,它将被删除,否则它将被添加。因此,只有包含奇数次(意味着只有一次)的元素保留下来。只有当元素在每个数组中都是唯一的时候,这才会起作用:

const entries = new Map();


for(const el of [...firstArray, ...secondArray]) {
const key = toHash(el);
if(entries.has(key)) {
entries.delete(key);
} else {
entries.set(key, el);
}
}


const result = [...entries.values()];

const firstArray = [
{ value: "0", display: "Jamsheer" },
{ value: "1", display: "Muhammed" },
{ value: "2", display: "Ravi" },
{ value: "3", display: "Ajmal" },
{ value: "4", display: "Ryan" }
]


const secondArray = [
{ value: "0", display: "Jamsheer" },
{ value: "1", display: "Muhammed" },
{ value: "2", display: "Ravi" },
{ value: "3", display: "Ajmal" },
];


const toHash = value => value.value + "@" + value.display;


const entries = new Map();


for(const el of [...firstArray, ...secondArray]) {
const key = toHash(el);
if(entries.has(key)) {
entries.delete(key);
} else {
entries.set(key, el);
}
}
  

const result = [...entries.values()];


console.log(result);

你可以在 b 上做 diff a,在 a 上做 diff b,然后合并两个结果

let a = [
{ value: "0", display: "Jamsheer" },
{ value: "1", display: "Muhammed" },
{ value: "2", display: "Ravi" },
{ value: "3", display: "Ajmal" },
{ value: "4", display: "Ryan" }
]


let b = [
{ value: "0", display: "Jamsheer" },
{ value: "1", display: "Muhammed" },
{ value: "2", display: "Ravi" },
{ value: "3", display: "Ajmal" }
]


// b diff a
let resultA = b.filter(elm => !a.map(elm => JSON.stringify(elm)).includes(JSON.stringify(elm)));


// a diff b
let resultB = a.filter(elm => !b.map(elm => JSON.stringify(elm)).includes(JSON.stringify(elm)));


// show merge
console.log([...resultA, ...resultB]);

我是在寻找一种方法来挑选一个数组中与另一个数组中的任何值都不匹配的第一个项目时遇到这个问题的,并设法最终用 array.find ()和 array.filter ()像这样排序

var carList= ['mercedes', 'lamborghini', 'bmw', 'honda', 'chrysler'];
var declinedOptions = ['mercedes', 'lamborghini'];


const nextOption = carList.find(car=>{
const duplicate = declinedOptions.filter(declined=> {
return declined === car
})
console.log('duplicate:',duplicate) //should list out each declined option
if(duplicate.length === 0){//if theres no duplicate, thats the nextOption
return car
}
})


console.log('nextOption:', nextOption);
//expected outputs
//duplicate: mercedes
//duplicate: lamborghini
//duplicate: []
//nextOption: bmw

如果您需要在交叉检查下一个最佳选项之前不断获取更新的列表,那么这个选项应该足够有效:)

此外,假设两个对象数组具有不同的 key value

// Array Object 1
const arrayObjOne = [
{ userId: "1", display: "Jamsheer" },
{ userId: "2", display: "Muhammed" },
{ userId: "3", display: "Ravi" },
{ userId: "4", display: "Ajmal" },
{ userId: "5", display: "Ryan" }
]


// Array Object 2
const arrayObjTwo =[
{ empId: "1", display: "Jamsheer", designation:"Jr. Officer" },
{ empId: "2", display: "Muhammed", designation:"Jr. Officer" },
{ empId: "3", display: "Ravi", designation:"Sr. Officer" },
{ empId: "4", display: "Ajmal", designation:"Ast. Manager" },
]

可以在 es5native js中使用 filter来减去两个数组对象。

//Find data that are in arrayObjOne but not in arrayObjTwo
var uniqueResultArrayObjOne = arrayObjOne.filter(function(objOne) {
return !arrayObjTwo.some(function(objTwo) {
return objOne.userId == objTwo.empId;
});
});

ES6中,你可以在 物体解构ES6时使用 Arrow 函数。

const ResultArrayObjOne = arrayObjOne.filter(({ userId: userId }) => !arrayObjTwo.some(({ empId: empId }) => empId === userId));


console.log(ResultArrayObjOne);

let obj1 =[
{ id: 1, submenu_name: 'login' },
{ id: 2, submenu_name: 'Profile',},
{ id: 3, submenu_name: 'password',  },
{ id: 4, submenu_name: 'reset',}
] ;
let obj2 =[
{ id: 2},
{ id: 3 },
] ;
               

// Need Similar obj
const result1 = obj1.filter(function(o1){
return obj2.some(function(o2){
return o1.id == o2.id;          // id is unnique both array object
});
});
console.log(result1);






// Need differnt obj
const result2 = obj1.filter(function(o1){
return !obj2.some(function(o2){    //  for diffrent we use NOT (!) befor obj2 here
return o1.id == o2.id;          // id is unnique both array object
});
});
console.log(result2);

最简单的方法是同时使用 过滤器一些 请参阅以下连结 ObjectArrayOfObjectInSimpleWay 的差异

大多数观察到的代码并不检查整个对象,而只是比较特定方法的值。此解决方案是相同的,只是您可以自己指定该方法。

这里有一个例子:

const arr1 = [
{
id: 1,
name: "Tom",
scores: {
math: 80,
science: 100
}
},
{
id: 2,
name: "John",
scores: {
math: 50,
science: 70
}
}
];
const arr2 = [
{
id: 1,
name: "Tom",
scores: {
math: 80,
science: 70
}
}
];


function getDifference(array1, array2, attr) {
return array1.filter(object1 => {
return !array2.some(object2 => {
return eval("object1." + attr + " == object2." + attr);
});
});
}


// 👇️ [{id: 2, name: 'John'...
console.log(getDifference(arr1, arr2, "id"));


// 👇️ [{id: 2, name: 'John'...
console.log(getDifference(arr1, arr2, "scores.math"));


// 👇️ [{id: 1, name: 'Tom'...
console.log(getDifference(arr1, arr2, "scores.science"));

有:

const array = [{id:3, name:'xx'} , {id:7, name:'yy'}, {id:9, name:'zz'}];
const array2 =[3,4,5];//These are a group of ids

我创建了一个函数,它从 array2中匹配 de id 的 array 中删除对象,如下所示:

export const aFilter = (array, array2) => {
   

array2.forEach(element => {
array = array.filter(item=> item.id !== element);
});
return array;
}

在调用该函数之后,我们应该得到一个没有对象的数组,其中 id = 3

const rta = aFilter(array, array2);
//rta should be = [{id:7, name:'yy'}, {id:9, name:'zz'}];

对我来说很有效,而且很简单