43
_.intersection([], [])

only works with primitive types, right?

It doesn't work with objects. How can I make it work with objects (maybe by checking the "Id" field)?

var a = [ {'id': 1, 'name': 'jake' }, {'id':4, 'name': 'jenny'} ]
var b = [ {'id': 1, 'name': 'jake' }, {'id': 9, 'name': 'nick'} ]

In this example, the result should be:

_.intersection(a, b);

[ {'id': 1, 'name': 'jake' } ];

Charles
  • 50,943
  • 13
  • 104
  • 142
user847495
  • 9,831
  • 17
  • 45
  • 48
  • 23
    It's kind-of amazing that none of those APIs allow an equality-tester function to be passed in. – Pointy Dec 29 '11 at 19:53
  • Of course it works with objects. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness – sam Nov 27 '19 at 20:11

10 Answers10

26

You can create another function based on underscore's function. You only have to change one line of code from the original function:

_.intersectionObjects = function(array) {
    var slice = Array.prototype.slice; // added this line as a utility
    var rest = slice.call(arguments, 1);
    return _.filter(_.uniq(array), function(item) {
      return _.every(rest, function(other) {
        //return _.indexOf(other, item) >= 0;
        return _.any(other, function(element) { return _.isEqual(element, item); });
      });
    });
  };

In this case you'd now be using underscore's isEqual() method instead of JavaScript's equality comparer. I tried it with your example and it worked. Here is an excerpt from underscore's documentation regarding the isEqual function:

_.isEqual(object, other) 
Performs an optimized deep comparison between the two objects, to determine if they should be considered equal.

You can find the documentation here: http://documentcloud.github.com/underscore/#isEqual

I put up the code on jsFiddle so you can test and confirm it: http://jsfiddle.net/luisperezphd/jrJxT/

Luis Perez
  • 27,650
  • 10
  • 79
  • 80
  • 4
    That's 3 loops so basically O(n^3) surely you can do better. (try O(nlogn) – Raynos Dec 29 '11 at 20:30
  • 1
    Good point Raynos, I was trying to go as much as possible with what was already there, this was just a small of underscore's existing intersect() function which he mentions using and therefore who's performance I assume he's happy with. – Luis Perez Dec 30 '11 at 18:49
  • Raynos, I provided an alternative algorithm below that you *might* like better. – Luis Perez Dec 30 '11 at 19:36
  • I used this and it works great (and fast enough for me). Changing the _.isEqual to an equality based on Id was the easiest way for a speedup. – SyntaxRules Sep 04 '14 at 18:59
  • Hold on a sec, isn't intersection for two hashsets O(|smaller set|)? Like, linear, not lin-log? Let alone cubic or quadratic... Edit: Oh i guess I was thinking of the operation for getting the intersection on the set of keys of an object. – Steven Lu Nov 20 '14 at 07:05
25

Here is an alternative algorithm that should be flexible and perform better. One of those improvements is that you can specify your own comparison function so in your case you can just compare the id if it's a unique identifier.

function intersectionObjects2(a, b, areEqualFunction) {
    var results = [];

    for(var i = 0; i < a.length; i++) {
        var aElement = a[i];
        var existsInB = _.any(b, function(bElement) { return areEqualFunction(bElement, aElement); });

        if(existsInB) {
            results.push(aElement);
        }
    }

    return results;
}

function intersectionObjects() {
    var results = arguments[0];
    var lastArgument = arguments[arguments.length - 1];
    var arrayCount = arguments.length;
    var areEqualFunction = _.isEqual;

    if(typeof lastArgument === "function") {
        areEqualFunction = lastArgument;
        arrayCount--;
    }

    for(var i = 1; i < arrayCount ; i++) {
        var array = arguments[i];
        results = intersectionObjects2(results, array, areEqualFunction);
        if(results.length === 0) break;
    }

    return results;
}

You can use it like this:

var a = [ { id: 1, name: 'jake' }, { id: 4, name: 'jenny'} ];
var b = [ { id: 1, name: 'jake' }, { id: 9, name: 'nick'} ];
var c = [ { id: 1, name: 'jake' }, { id: 4, name: 'jenny'}, { id: 9, name: 'nick'} ];

var result = intersectionObjects(a, b, c, function(item1, item2) {
    return item1.id === item2.id;
});

Or you can leave out the function and it will use underscores _.isEqual() function, like so:

var result = intersectionObjects(a, b, c);

You can find it on jsFiddle here: http://jsfiddle.net/luisperezphd/43vksdn6/

Luis Perez
  • 27,650
  • 10
  • 79
  • 80
  • Thanks, this helped me a lot in my current project. – Bryan Bailliache Nov 29 '12 at 19:29
  • BEst Answer, because it gives possibility to filter on ceratin element – Lightning3 Jan 08 '15 at 07:10
  • Thanks! I do not get why we need the `if(Results.length === 0) break;` part though. Can someone explains to me why this is needed? – Alexandre Bourlier Apr 14 '15 at 08:43
  • 1
    @AlexandreBourlier The function `intersectionObjects()` accepts multiple arrays. It returns any objects that exists in every single array passed to it. The moment it finds an object doesn't exist in one of the arrays it doesn't bother checking against the rest of the arrays. That's why it breaks. – Luis Perez Apr 14 '15 at 20:05
  • Of course... That is obvious. I cant' tell why I wasn't seeing it. Thanks for your explanation – Alexandre Bourlier Apr 15 '15 at 10:30
  • @AlexandreBourlier, this is the same (very ineffective) `O(n^3)` as `_.intersectionObjects`. Well, actually it's `O(n^2)` but only because you removed handling of multiple arrays which obviously removed the needed in an extra loop. But the functionality got reduced. – meandre Dec 11 '15 at 11:43
  • can anyone post a pure js solution of this by removing underscore – django Feb 18 '16 at 12:49
5

The array methods in underscore are very powerful, you should only need a few lines to accomplish what you want to do:

var a = [ {'id': 1, 'name': 'jake' }, {'id':4, 'name': 'jenny'} ];
var b = [ {'id': 1, 'name': 'jake' }, {'id': 9, 'name': 'nick'} ];

var result = _(a).chain().map(function(ea) {
    return _.find(b, function(eb) {return ea.id == eb.id;});
}).compact().value();

If you have large arrays you can get rid of the compact() call with one additional line:

var result = [];
_.each(a, function(ea) {
    var entry = _.find(b, function(eb) {return ea.id == eb.id;});
    if (entry) result.push(entry);
});
Julian D.
  • 5,464
  • 1
  • 25
  • 35
  • 1
    It is quadratic in time and that is bad – user2846569 Nov 20 '13 at 11:16
  • True, it is O(N * M). Depending on the size of your data set (e.g. a few hundred items) this will most likely not be a problem at all. If it is, you will probably need a completely different approach: use sorted Arrays, create some sort of indexing... – Julian D. Nov 20 '13 at 15:44
  • In most cases you are right, but you could miss the point where data grows and performance degrades and other thing is if you have code like that in most places, lets say with 100 items it is 100x times slower, and for big app it could get noticeable. But of course always each case should be studied separately. – user2846569 Nov 20 '13 at 20:49
4

I'd like to share my general solution for those cases.

I added a general function to underscore, using mixin, which performs a binary 'array' operation on two collections, according to a given Hash function:

_.mixin({
    collectionOperation: function(arr1, arr2, hash, action) {
        var iArr1 = _(arr1).indexBy(hash)
            , iArr2 = _(arr2).indexBy(hash);
        return action(_(iArr1).keys(), _(iArr2).keys()).map(function (id) {
            return iArr1[id] || iArr2[id];
        });
    }
});

Usage example:

_([{id:1,v:'q'},{id:2,v:'p'}]).collectionOperation([{id:3,v:'pq'}], 'id', _.union )

Note that 'id' may be replaced with a function.

I believe this solution is O(n+m).

Jacob
  • 133
  • 1
  • 1
  • 10
  • A performance test would be awesome, something like (http://stackoverflow.com/a/12074451) http://jsfiddle.net/neoswf/aXzWw/ or jsperf.com – SDK Feb 09 '16 at 15:09
4

In lodash 4.0.0. We can try like this

var a = [ {'id': 1, 'name': 'jake' }, {'id':4, 'name': 'jenny'} ];
var b = [ {'id': 1, 'name': 'jake' }, {'id': 9, 'name': 'nick'} ];

_.intersectionBy(a, b, 'id');

Output:

[ {'id': 1, 'name': 'jake' } ];

Raja Jaganathan
  • 33,099
  • 4
  • 30
  • 33
3

Technically, it does work on objects, but you need to be careful of reference equality.

var jake = {'id': 1, 'name': 'jake' },
    jenny = {'id':4, 'name': 'jenny'},
    nick =  {'id': 9, 'name': 'nick'};
var a = [jake, jenny]
var b = [jake, nick];

_.intersection(a, b);
// is
[jake]
Joe
  • 80,724
  • 18
  • 127
  • 145
2
var a = [ {'id': 1, 'name': 'jake' }, {'id':4, 'name': 'jenny'} ];
var b = [ {'id': 1, 'name': 'jake' }, {'id': 9, 'name': 'nick'} ];

Working function:

 function intersection(a,b){
  var c=[];
   for(m in a){
      for(n in b){
         if((a[m].id==a[n].id)&&(a[m].name==b[n].name))
                 c.push(a[m]);          
      }}
    return c;
  }
console.log(intersection(a,b));

I have also tried code in jQuery specially after Pointy's suggestion. Compare has to be customizable as per the structure of JSON object.

<script type="text/javascript">
jQuery(document).ready(function(){
    var a = [ {'id': 1, 'name': 'jake' }, {'id':4, 'name': 'jenny'} ];
    var b = [ {'id': 1, 'name': 'jake' }, {'id': 9, 'name': 'nick'} ];
    var c=[];
    jQuery.each(a, function(ka,va) {
       jQuery.each(b, function(kb,vb) {      
                if(compare(va,vb))
                    c.push(va); 
     });   
    });
     console.log(c);  
});
function compare(a,b){
  if(a.id==b.id&&a.name==b.name)
     return true;
  else return false;
}
</script>
Umesh Patil
  • 10,475
  • 16
  • 52
  • 80
  • @Pointy. So what can be done ? Here, for..in iterate all the elements in an object. The basic objective here is to compare all the sub-objects with that in another. – Umesh Patil Dec 29 '11 at 19:39
  • Well when Array instances are passed the code could iterate over the numeric indexes. The problem with "for ... in" is that it can get confused because it will include properties that may be there on the Array prototype object. – Pointy Dec 29 '11 at 19:51
  • Well yes using ".each()" is a fine idea too :-) – Pointy Dec 29 '11 at 20:30
0

If you wanna compare only objects:

b = {"1":{"prod":"fibaro"},"2":{"prod":"aeotec"},"3":{"prod":"sw"}}; 
a = {"1":{"prod":"fibaro"}};


_.intersectObjects = function(a,b){
    var m = Object.keys(a).length;
    var n = Object.keys(b).length;
    var output;
    if (m > n) output = _.clone(a); else output = _.clone(b);

    var keys = _.xor(_.keys(a),_.keys(b));
    for(k in keys){
        console.log(k);
        delete output[keys[k]];
    }
    return output;
}
_.intersectObjects(a,b); // this returns { '1': { prod: 'fibaro' } }
gdm
  • 7,647
  • 3
  • 41
  • 71
0
//nested array is in the format of [[],[],[]]

function objectArrayIntersection(nestedArrays){     

    let intersectingItems = [];                
    let uniqArr = _.uniq(_.flatten(nestedArrays)); //intersecting items removed    
    const countOfNestedArrays = nestedArrays.length;


    for (let index = 0; index < uniqArr.length; index++) {
        let uniqItem = uniqArr[index];
        let foundCount = 0;

        for(var j = 0;j<countOfNestedArrays;j++){
            var i = _.indexOf(nestedArrays[j],uniqItem);
            if(i != -1)
                foundCount ++;
        }

        if(foundCount ==  countOfNestedArrays){
            intersectingItems.push(uniqItem);
        }
    }

    return intersectingItems;
}

I tried solving it this way.

Derese Getachew
  • 440
  • 6
  • 10
-2
var a = {a:'a1',b:'b1'},
    b = {a:'a2',b:'b2',c:'c2'};

_.pick(a,_.intersection(_.keys(a),_.keys(b)));

// {a:'a1',b:'b1'}
Igor Timoshenko
  • 1,001
  • 3
  • 14
  • 27
  • 1
    This is wrong, the comparison is done only on the keys: _.keys(a) returns ["a", "b"] and _.keys(b) returns ["a", "b", "c"]. – tanguy_k Feb 09 '14 at 20:37