35

I've 2 array of objects that I'd deeply compare with lodash

However, I've a prob with it:

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

How should I compare to see that both are equal?

ryeballar
  • 29,658
  • 10
  • 65
  • 74
Archer
  • 5,073
  • 8
  • 50
  • 96

5 Answers5

60

You can make use of differenceWith() with an isEqual() comparator, and invoke isEmpty to check if they are equal or not.

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>

UPDATE June 22, 2018

This update is in response to the comment below:

@ryeballar if any of the array is undefined it returns true. How would you solve this. Thanks in advance buddy

As stated in the differenceWith documentation:

The order and references of result values are determined by the first array.

This means that as long as all the items in the first array will match everything else in the second array, then the resulting array from the differenceWith invocation will be empty.

An alternative solution that truly solves the problem is to use xorWith() with the same chain of functions from the solution above.

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>
ryeballar
  • 29,658
  • 10
  • 65
  • 74
  • Thanks @ryeballar . Basically I really need to find `diff` between them, so `_.difference` should be just replaced with `_.differenceWith` with `_.isEqual` comparator. – Archer May 06 '16 at 06:54
  • 4
    I've never seen syntax for `_(foo)` mentioned anywhere in lodash. Where is this in the docs? – Don P May 19 '17 at 19:28
  • 3
    @DonP You may refer to [this part of the lodash documentation](https://lodash.com/docs/4.17.4#lodash). – ryeballar May 20 '17 at 00:48
  • 1
    @ryeballar if any of the array is undefined it returns true. How would you solve this. Thanks in advance buddy – Uzumaki Naruto Jun 22 '18 at 12:10
  • @UzumakiNaruto I added an update, this answer is already quite outdated. – ryeballar Jun 22 '18 at 13:27
  • 2
    @ryeballar , could you show, how I can achieve `_(x)` with individually installed components, because I do not need the whole `lodash`? I found `lodash.isequal` (ie `npm i --save lodash.isequal`), `lodash.xorwith`, `lodash.isequal`, `lodash.isempty`, but I do not know what to install for `_(x)`. – TitanFighter Jun 12 '19 at 21:40
  • 4
    You can simply require each function and then use them as you would in a non-chained manner. `isEmpty(xorWith(x, y, isEqual))` – ryeballar Jun 22 '19 at 13:01
  • How about `IsEmpty(differenceWith(x,y,IsEqual)) && isEmpty(differenceWith(y,x,IsEqual))`? Looks easier to write, regardless of whether it takes two iterations instead of one. Simplicity might make it faster for small cases anyway. – Craig Hicks Feb 20 '21 at 06:08
30

Following @ryeballar answer, if you only want to import specific lodash methods you can use this notation:

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


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

Anita
  • 2,741
  • 27
  • 28
5

Both answers on above with xorWith & differenceWith do not take in count if the array has different length only if the current item is found in the second array.

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>

In that particular case, we also would have to compare both arrays length.

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>
Angel Fraga Parodi
  • 750
  • 10
  • 20
2

I prefer pure JS since i haven't got the patience to learn underscore or lodash. So i invent something i have been long dreaming of. The Object.prototype.compare(). The v0.0.2 is doing only shallow comparison though but adequate for this question.

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

Cool... So then lets continue with the question... I guess... since we already have an Object.prototype.compare() there should be absolutely no harm in the invention of Array.prototype.compare(). Lets make it more clever this time. It shall tell primitives from objects. One other thing is, arrays are ordered; so in my book [1,2] is not equal to [2,1]. Also this makes the job simpler.

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
Redu
  • 25,060
  • 6
  • 56
  • 76
  • 2
    Your solution looks cool, thanks. However: `new Object({a:1, b:2, c:3}).compare({c:3,b:2,a:1,d:0});` returns `true`, but should be `false` Same problems with deep object comparison. I'd prefer existing `lodash` solution. – Archer May 12 '16 at 07:51
  • @Archer: hmm cool.. that's why it's v0.0.1. Seems we have to add a check for `Object.keys().length` stage too. – Redu May 12 '16 at 07:55
  • @Archer: did a quick fix. I guess next stage could be to make it dive deeper recursively, hand to hand with it's twin `Array.prototype.compare()` – Redu May 12 '16 at 08:17
  • good ;) But better to learn `lodash`. It's what it was designed for. – Archer May 12 '16 at 08:26
  • 2
    @Archer Well may be... But I am a very library agnostic person. Think it this way, if you need a banana you wouldn't like a gorilla knocking at your door holding a banana with the whole jungle behind... – Redu May 12 '16 at 17:27
  • Sometime you need many different fruits and gorilla might get them to you... Better this way rather then plant all those fruits yourself. – Archer May 12 '16 at 20:38
  • 4
    Nice effort but there's a god-like term for these cases: 'Don't reinvent the wheel'. Anyway, javascript es6 should natively include array and object comparisons with shallow and deep flags in my humble opinion. – tommyalvarez May 10 '18 at 00:11
  • How can I use your method in Angular with typescript? – Matley Jan 02 '21 at 11:03
0

Combining answers from solution 1 & solution 2, with examples.

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>

In Js solutions, one line code :

import { size, isEmpty, isEqual, xorWith } from "lodash";    
export const isArrayOfObjectsEqual = (x, y) => size(x) === size(y) && isEmpty(xorWith(x, y, isEqual));