How to use underscore's “intersection” on objects?

_.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' } ];

Answers:

Answer

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/

Answer

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/

Answer

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);
});
Answer

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).

Answer

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

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

Answer
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>
Answer

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' } }
Answer
//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.

Answer
var a = {a:'a1',b:'b1'},
    b = {a:'a2',b:'b2',c:'c2'};

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

// {a:'a1',b:'b1'}

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.