使用Lodash进行对象数组的深度比较

36

我有两个对象数组,想用lodash进行深度比较。

但是,我遇到了一个问题:

> var x = [{a:1, b:2}, {c:3, d:4}];
> var y = [{b:2, a:1}, {d:4, c:3}];
> _.difference(x,y, _.isEqual);
[ { a: 1, b: 2 }, { c: 3, d: 4 } ]

我应该如何比较以确保它们相等?


可以对数组的数组进行排序。但是对于对象的数组则不行。 - Archer
5个回答

61
您可以使用带有 isEqual() 比较器的 differenceWith(),并调用 isEmpty() 来检查它们是否相等。

var isArrayEqual = function(x, y) {
  return _(x).differenceWith(y, _.isEqual).isEmpty();
};

var result1 = isArrayEqual(
  [{a:1, b:2}, {c:3, d:4}],
  [{b:2, a:1}, {d:4, c:3}]
);

var result2 = isArrayEqual(
  [{a:1, b:2, c: 1}, {c:3, d:4}],
  [{b:2, a:1}, {d:4, c:3}]
);

document.write([
  '<div><label>result1: ', result1, '</label></div>',
  '<div><label>result2: ', result2, '</label></div>',
].join(''));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.11.2/lodash.js"></script>

2018年6月22日更新:这个更新是针对下面的评论:

@ryeballar 如果数组中有任何一个未定义,则返回 true。你怎么解决这个问题呢?提前感谢你哥们。

differenceWith文档所述:

结果值的顺序和引用由第一个数组确定。

这意味着只要第一个数组中的所有项与第二个数组中的其他所有内容匹配,那么从differenceWith调用中产生的结果数组将为空。

一个真正解决问题的替代方案是使用xorWith()与上面解决方案相同的函数链。

var isArrayEqual = function(x, y) {
  return _(x).xorWith(y, _.isEqual).isEmpty();
};

var result1 = isArrayEqual(
  [{a:1, b:2}, {c:3, d:4}],
  [{b:2, a:1}, {d:4, c:3}]
);

var result2 = isArrayEqual(
  [{a:1, b:2, c: 1}, {c:3, d:4}],
  [{b:2, a:1}, {d:4, c:3}]
);

var result3 = isArrayEqual(
  [{a:1, b:2, c: 1}, {c:3, d:4}],
  [{b:2, a:1}, {d:4, c:3}, undefined]
);

console.log('result1:', result1);
console.log('result2:', result2);
console.log('result3:', result3);
.as-console-wrapper{min-height:100%;top:0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>


4
我从未在 lodash 的任何地方看到 _(foo) 语法的提及。这在文档的哪里可以找到? - Don P
3
您可以参考 lodash文档 中的这一部分。 - ryeballar
1
@ryeballar 如果数组中有任何一个未定义,它就会返回 true。你会如何解决这个问题?提前感谢你的帮助。 - Uzumaki Naruto
2
@ryeballar,你能展示一下我如何使用单独安装的组件来实现_(x)吗?因为我不需要整个lodash。我找到了lodash.isequal(即npm i --save lodash.isequal)、lodash.xorwithlodash.isequallodash.isempty,但我不知道要安装什么才能得到_(x) - TitanFighter
4
你可以简单地调用每个函数,然后像非链式编程一样使用它们。isEmpty(xorWith(x, y, isEqual)) - ryeballar
显示剩余3条评论

32

根据@ryeballar的回答,如果你只想引入特定的lodash方法,你可以使用以下表示法:

import { isEmpty, isEqual, xorWith } from 'lodash';


export const isArrayEqual = (x, y) => isEmpty(xorWith(x, y, isEqual));


1
好的解决方案,谢谢。 - Archer
太棒了,这个有CodeSandbox支持 https://codesandbox.io/s/cold-fog-30b7d1?file=%2Fsrc%2FApp.vue - vAhy_The
这个答案;非常感谢。我把它命名为isEquivalent,因为你实际上可以传递任何两个数组/对象/值/... - undefined

5
上面的两个答案都没有考虑数组长度不同的情况,只有在第二个数组中找到当前项时才会计算。使用xorWithdifferenceWith方法。

var isArrayEqual = function(x, y) {
  return _(x).xorWith(y, _.isEqual).isEmpty();
};

var result = isArrayEqual(
  [{a:1, b:2}],
  [{a:1, b:2}, {a:1, b:2}]
);


console.log('result should be false:', result);
.as-console-wrapper{min-height:100%;top:0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>

在这种情况下,我们还需要比较两个数组的长度。

const isArrayEqual = function(x, y) {
  const isSameSize = _.size(x) === _.size(y);
  return isSameSize && _(x).xorWith(y, _.isEqual).isEmpty();
};

const result = isArrayEqual(
  [{a:1, b:2}],
  [{a:1, b:2}, {a:1, b:2}]
);


console.log('result should be false:', result);
.as-console-wrapper{min-height:100%;top:0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>


感谢你包含了这个检查。其他方法可能会得到错误的结果。 - Angus Ryer

2

由于我没有耐心学习underscore或lodash,因此我更喜欢纯JS。所以我发明了一些我长期梦想的东西:Object.prototype.compare()。尽管v0.0.2仅进行浅层比较,但对于这个问题已经足够了。

Object.prototype.compare = function(o){
  var ok = Object.keys(this);
  return typeof o === "object" && ok.length === Object.keys(o).length ? ok.every(k => this[k] === o[k]) : false;
};
var obj1 = {a:1,b:2,c:3},
    obj2 = {c:3,a:1,b:2},
    obj3 = {b:2,c:3,a:7};

document.write ("<pre>" + obj1.compare(obj2) + "</pre>\n");
document.write ("<pre>" + obj2.compare(obj3) + "</pre>\n");
document.write ("<pre>" + new Object({a:1, b:2, c:3}).compare({c:3,b:2,a:1,d:0}) + "</pre>\n");

很好,那么让我们继续这个问题吧...我猜...既然我们已经有了一个Object.prototype.compare(),那么发明Array.prototype.compare()应该没有任何问题。这次让它更聪明一些。它应该能区分基本类型和对象。另外,数组是有序的;所以在我的书里,[1,2]不等于[2,1]。这也使得工作更简单。

Object.prototype.compare = function(o){
  var ok = Object.keys(this);
  return typeof o === "object" && ok.length === Object.keys(o).length ? ok.every(k => this[k] === o[k]) : false;
};
Array.prototype.compare = function(a){
  return this.every((e,i) => typeof a[i] === "object" ? a[i].compare(e) : a[i] === e);
}
var x = [{a:1, b:2}, {c:3, d:4}],
    y = [{b:2, a:1}, {d:4, c:3}],
    a = [1,2,3,4,5],
    b = [1,2,3,4,5],
    p = "fourtytwo",
    r = "thirtyseven",
    n = 42,
    m = 37;
document.writeln(x.compare(y)); // the question is answered here
document.writeln(a.compare(b));
document.writeln(p.compare(r)); // these primitives end up at Object prototype
document.writeln(n.compare(m)); // so modify Object.prototype.compare () accordingly


2
你的解决方案看起来很酷,谢谢。然而:new Object({a:1, b:2, c:3}).compare({c:3,b:2,a:1,d:0}); 返回值为 true,但应该是 false。深度对象比较也存在相同的问题。我更倾向于使用现有的 lodash 解决方案。 - Archer
@Archer: 快速修复了一下。我猜下一步可能是让它递归地深入,与它的孪生函数 Array.prototype.compare() 手牵手。 - Redu
不错 ;) 但最好学习 lodash。这就是它的设计初衷。 - Archer
2
@Archer 可能吧...但我是一个非常不偏爱库的人。这样想,如果你需要香蕉,你不会希望一只猩猩拿着整个丛林的香蕉敲门... - Redu
4
不错的努力,但这种情况有一个神一般的词:'不要重复造轮子'。无论如何,在我看来,JavaScript ES6应该本地支持数组和对象的比较,并带有浅层和深层标志。 - tommyalvarez
显示剩余3条评论

0
结合解决方案1解决方案2的答案,附带示例。

const isArrayEqual = (x, y) => (
_.size(x) === _.size(y) && _.isEmpty(_.xorWith(x, y, _.isEqual))
);

const result1 = isArrayEqual(
  [{a:1, b:2}],
  [{a:1, b:2}, {a:1, b:2}]
);

const result2 = isArrayEqual(
  [{a:1, b:2},{a:2, b:2}],
  [{a:1, b:2}, {a:1, b:2}]
);

const result3 = isArrayEqual(
  [{a:1, b:2},{a:1, b:2}],
  [{a:1, b:2}, {a:1, b:2}]
);

// if order is changed
const result4 = isArrayEqual(
  [{a:2, b:2},{a:1, b:2}],
  [{a:1, b:2}, {a:2, b:2}]
);

console.log('result1 should be false:', result1);
console.log('result2 should be false:', result2);
console.log('result3 should be true:', result3);
console.log('result4 should be true:', result4);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>

在Js解决方案中,一行代码:
import { size, isEmpty, isEqual, xorWith } from "lodash";    
export const isArrayOfObjectsEqual = (x, y) => size(x) === size(y) && isEmpty(xorWith(x, y, isEqual));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接