44
_.difference([], [])

this method works fine when i'm having primitive type data like

var a = [1,2,3,4];
var b = [2,5,6];

and the _.difference(a,b) call returns [1,3,4]

but in case i'm using object like

var a = [{'id':1, 'value':10}, {'id':2, 'value':20}];
var b = [{'id':1, 'value':10}, {'id':4, 'value':40}];

doesn't seem to work

mahemoff
  • 44,526
  • 36
  • 160
  • 222
Nas
  • 887
  • 1
  • 11
  • 21
  • similar (not duplicate) to http://stackoverflow.com/questions/8672383/how-to-use-underscores-intersection-on-objects – drzaus Sep 03 '14 at 17:51
  • and if you came here looking for the difference between two objects (i.e. their delta, not between arrays of objects), there's nothing built-in to Underscore but you could try http://stackoverflow.com/a/25651677/1037948 – drzaus Sep 03 '14 at 18:58
  • 1
    Notice that _difference(a,b) will return [1,3,4]. _.difference method will return the elements of the first array that aren't present in the __other arrays__ – Rene Hernandez Feb 04 '16 at 22:03

8 Answers8

60

try this on for size for finding the difference of an array of objects:

var test = [{a: 1},{b: 2}];
var test2 = [{a: 1}];

_.filter(test, function(obj){ return !_.findWhere(test2, obj); });
asgeo1
  • 9,028
  • 6
  • 63
  • 85
kontr0l
  • 601
  • 1
  • 5
  • 2
32

While the accepted answer is correct, and the other answers give good ideas as well, there is an additional option that's pretty easy to implement with underscore.

This solution relies on each object having a unique ID, but in many cases this will be true, and you can get the difference of two arrays of objects in just two lines of code.

Using underscore's "pluck" method, you can quickly construct an array of all of the ID's in your source set and the target set. From there, all of underscore's array methods will work, difference, union, intersection etc...

After the operation, it is trivial to obtain the list of objects from your source list that you desire. Here's an example:

Verbose:

var a = [{'id':1, 'value':10}, {'id':2, 'value':20}];
var b = [{'id':1, 'value':10}, {'id':4, 'value':40}];

var arr1 = _.pluck(a, "id");
var arr2 = _.pluck(b, "id");
var diff = _.difference(arr1, arr2);
var result = _.filter(a, function(obj) { return diff.indexOf(obj.id) >= 0; });

or, more concisely:

var diff = _.difference(_.pluck(a, "id"), _.pluck(b, "id"));
var result = _.filter(a, function(obj) { return diff.indexOf(obj.id) >= 0; });

Of course, this same technique can be extended for use with any of the array methods.

Clayton Gulick
  • 9,755
  • 2
  • 36
  • 26
  • 5
    Great answer, I liked kontr0l but Angular likes to add a hashkey property to objects so it makes it challenging to compare the entire object if what you're comparing with doesn't have the hashkey yet. With your answer I can compare on ID alone which works perfectly. – Batman Dec 26 '15 at 20:29
  • **warning** pluck don't exist, read this [changelog lodash 4.0.0](http://stackoverflow.com/questions/35136306/what-happened-to-lodash-pluck) now you can use **_.map** – maliness Jul 18 '16 at 08:44
  • 2
    @maliness, to clarify, pluck does not exist for for **lodash**, but it *does* exist in underscore still, which is what the question is referring to. – big_water Jan 13 '17 at 15:51
16

Reason is simply that object with same content are not same objects e.g.

var a = [{'id':1, 'value':10}, {'id':2, 'value':20}]; 
a.indexOf({'id':1, 'value':10})

It will not return 0 but -1 because we are searching for a different object

See the source code http://underscorejs.org/underscore.js, _.difference uses _.contains

_.difference = function(array) {
  var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
  return _.filter(array, function(value){ return !_.contains(rest, value); });
};

and _.contains ultimately uses indexOf hence will not find objects unless they point to same object.

You can improve the underscore _.contains by looping through all items and calling a compare callback, which you should be able to pass to difference or contains function or you can check this version which improves contains methods

Anurag Uniyal
  • 85,954
  • 40
  • 175
  • 219
4
without using underscorejs,
here is the pretty simple method i got solution ... 

a = [{'key':'123'},{'key':'222'},{'key':'333'}]
b = [{'key':'123'},{'key':'222'}]

var diff = a.filter(function(item1) {
  for (var i in b) {
    if (item1.key === b[i].key) { return false; }
  };
  return true;
});
console.log('result',diff)
Mohideen bin Mohammed
  • 18,813
  • 10
  • 112
  • 118
3

I actually can imagine situations where I'd rather use @kontr0l approach than something else, but you have to understand that this approach is quadratic, so basically this code is an abstraction for naïve approach - iterate through all values in two arrays.

There are approaches better than quadratic, I won't use here any big O notation, but here are two main approaches, both are better then naïve one:

  • iterate through one of the arrays and check for existence in sorted second array using binary search.
  • put values into set/hash/dictionary/you name it.

As it've been already mentioned, first approach can be adopted for objects if you reimplement standard difference method with using some more flexible analogue of indexOf method.

With second approach we can hit the wall with the fact that, as of Feb'2015, only modern browsers are supporting Sets. As of hashes (well, objects) in javascript, they can have only string-type keys, so any object invoked as key first shoud be converted via toString method. So, we need to provide some => correspondece. On practice in most cases it's pretty straightforward, for instance, for your particular example such correspondence can be just String(obj.id).

Having such correspondence, we also can use following lodas/undercore approach:

var idsA = _.pluck(a, 'id');
var idsB = _.pluck(b, 'id');

// actually here we can stop in some cases, because 
// quite often we need to identify object, but not the object itself - 
// for instance to send some ids through remote API.
var intersect = _.intersection(idsA, idsB);

//to be 100% sure you get the idea, here we assume that object having equal ids are treated as equal, so does not really matter which of arrays we'll iterate:

var dictA = _.object(idsA, a); // now we can find a by id faster then with _.find
var intersectObj = intersect.map(function(id) {return dictA[id})

But buy admitting slightly stricter restriction - that we can build correspondence between our set objects and natural numbers we can build even more efficent algorithm, i.e. all our ids are non-negative integers - we can use more efficient algorithm.

The trick is to implement set by introducing two helper arrays this way:

var naturalSet = function (arr) {
    var sparse = [];
    var dense = [];

    var contains = function (i) {
        var res = sparse[i] < dense.length && dense[sparse[i]] == i;
        return res;
    }

    var add = function (v) {
        if (!contains(v)) {
            sparse[v] = dense.length;
            dense.push(v);
        }
    }

    arr.forEach(add);

    return {
        contains: contains,
        toArray: function () {
            return dense
        },
        _getDense: function () {
            return dense
        },
        _getSparse: function () {
            return sparse
        }
    }
}

Then we can introduce set with mapping to naturalSet:

var set = function (arr, valueOf) {
    var natSet = naturalSet(arr.map(valueOf));
    return {
        contains: function (item) {
            return natSet.contains(valueOf(item))
        },
        toArray: function () {
            var sparse = natSet._getSparse();
            var res = natSet._getDense().map(function (i) {
                return arr[sparse[i]];
            });
            return res;
        }
    }
}

and finally, we can introduce intersection:

var intersection = function(arr1, arr2, valueOf) {
   return set(arr2.filter(set(arr1, valueOf).contains), valueOf).toArray();
}

So, relying on the structure of data you are working can help you sometimes.

user3335966
  • 2,673
  • 4
  • 30
  • 33
shabunc
  • 23,119
  • 19
  • 77
  • 102
2
var a = [{'id':1, 'value':10}, {'id':2, 'value':20}];
var b = [{'id':1, 'value':10}, {'id':4, 'value':40}];

var c = _.difference(a.map(e => e.id), b.map(e =>e.id));
var array = [];
array = a.map(e => {
   if(c.includes(e.id)){
     return e;
   }
}).filter(r=>r);
Anirudh
  • 41
  • 5
  • 1
    Welcome! Your answer will be more helpful (and more likely to receive upvotes) if you explain the advantages of your approach over the other answers. – divibisan Apr 12 '18 at 18:50
1

Don't get why these answers are so complex unless I'm missing something?

var a = [{'id':1, 'value':10}, {'id':2, 'value':20}];
var b = [{'id':1, 'value':10}, {'id':4, 'value':40}];

// Or use lodash _.differenceBy
const difference = (array1, array2, prop = 'id') =>
  array1.filter(item1 =>
    !array2.some(item2 =>
      item2[prop] === item1[prop],
    ),
  );
  
// In one array.
console.log(difference(a, b));

// Intersection.
console.log([...difference(a, b), ...difference(b, a)]);
Dominic
  • 62,658
  • 20
  • 139
  • 163
0

Forgive me for hopping in late here, but this may help:

array_of_objects = 
    // return the non-matching items (without the expected properties)
    _.difference(array_of_objects,
        // filter original list for items with expected properties
        _.where(
            // original list
            array_of_objects,
            // expected properties
            {'id':1, 'value':10}
        )
    )
drzaus
  • 24,171
  • 16
  • 142
  • 201
Gauss156
  • 86
  • 3