How to use underscore's "intersection" on objects? - javascript

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

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/

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/

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

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

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

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]

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>

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

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

var a = {a:'a1',b:'b1'},
b = {a:'a2',b:'b2',c:'c2'};
_.pick(a,_.intersection(_.keys(a),_.keys(b)));
// {a:'a1',b:'b1'}

Related

Create object from a property in an array of objects

I have an array of objects like this:
[
{id: 'id1', random: 'labels.prop1'},
{id: 'id2', random: 'labels.prop2'},
{id: 'id3', random: 'texts.anotherprop'}
]
Is there a short way to generate from that array an object based on the property random? I'd need this:
{
texts: {
anotherprop: ''
},
labels: {
prop1: '',
prop2: ''
}
}
You can use reduce() two times to build this nested object.
var data = [
{id: 'id1', random: 'labels.prop1'},
{id: 'id2', random: 'labels.prop2'},
{id: 'id3', random: 'texts.anotherprop'}
]
var result = data.reduce(function(r, o) {
var arr = o.random.split('.')
arr.reduce(function(a, b, i) {
return (i != arr.length - 1) ? a[b] || (a[b] = {}) : a[b] = ''
}, r)
return r;
}, {})
console.log(result)
You could iterate the array and build an object based on the parts of random.
function setValue(object, path, value) {
var fullPath = path.split('.'),
way = fullPath.slice(),
last = way.pop();
way.reduce(function (r, a) {
return r[a] = r[a] || {};
}, object)[last] = value;
}
var data = [{ id: 'id1', random: 'labels.prop1' }, { id: 'id2', random: 'labels.prop2' }, { id: 'id3', random: 'texts.anotherprop' }],
result = {};
data.forEach(function (o) {
setValue(result, o.random, '');
});
console.log(result);
var arr = [
{id: 'id1', random: 'labels.prop1'},
{id: 'id2', random: 'labels.prop2'},
{id: 'id3', random: 'texts.anotherprop'}
];
var result = {};
arr.forEach(function(o) {
var parts = o.random.split('.');
var r = result;
var i = 0;
for( ; i < parts.length - 1; i++) {
r[parts[i]] = r[parts[i]] || {};
r = r[parts[i]];
}
r[parts[i]] = '';
});
console.log(result);

conversion of underscore js based function to PlainJs or jquery

Problem:
I want to get inetrsection of array of objects.
var a = [{id: 1, name: 'jake'}];
var b = [{id: 1, name: 'jake'}, {id: 4,name: 'jenny'}];
var c = [{id: 1,name: 'jake'}, {id: 4,name: 'jenny'}, {id: 9,name: 'nick'}];
intersect (a,b,c);// Find Intersection based on id key
// answer would be [{id: 1, name: 'jake'}]
I found this very help answer here
How to use underscore's "intersection" on objects?
BUT
This solution uses underscore.js while i am using jquery.
I cant seems to know what _.any is doing.
Any Help will be appreciated.
Here is complete Code
CODE: http://jsfiddle.net/luisperezphd/43vksdn6/
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;
}
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;
});
This solution counts the same given objects with the same property and returns them if they in both of the arrays intersection().
function intersection(a, b, key) {
function count(a) {
o[a[key]] = o[a[key]] || { value: a, count: 0 };
o[a[key]].count++;
}
var o = {}, r = [];
a.forEach(count);
b.forEach(count);
Object.keys(o).forEach(function (k) {
o[k].count === 2 && r.push(o[k].value);
});
return r;
}
function intersect(a, b, c, key) {
return intersection(intersection(a, b, key), c, key);
}
var a = [{ id: 1, name: 'jake' }],
b = [{ id: 1, name: 'jake' }, { id: 4, name: 'jenny' }],
c = [{ id: 1, name: 'jake' }, { id: 4, name: 'jenny' }, { id: 9, name: 'nick' }],
result = intersect(a, b, c, 'id');
document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');
This works now with a callback in this style.
function (v) {
return v.id;
}
It needs to returns a stringable value and can contain other value and combinations like this example which intersects with name and age (if existing in the data):
function (v) {
return v.name + '|' + v.age;
}
function intersection(a, b, cb) {
function count(a) {
o[cb(a)] = o[cb(a)] || { value: a, count: 0 };
o[cb(a)].count++;
}
var o = {}, r = [];
a.forEach(count);
b.forEach(count);
Object.keys(o).forEach(function (k) {
o[k].count === 2 && r.push(o[k].value);
});
return r;
}
function intersect(a, b, c, key) {
return intersection(intersection(a, b, key), c, key);
}
var a = [{ id: 1, name: 'jake' }],
b = [{ id: 1, name: 'jake' }, { id: 4, name: 'jenny' }],
c = [{ id: 1, name: 'jake' }, { id: 4, name: 'jenny' }, { id: 9, name: 'nick' }],
result = intersect(a, b, c, function (_) { return _.id; });
document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');
Here is my answer:
Benefits are
it gives me freedom to intersect as many objects as i want
I can use Compare function where i can use equality or any logic I want
CODE:
var a = [ { id: 1, name: 'jake' } , { id: 4, name: 'jenny'}, { id: 9, name: 'nick'} ];
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 intersectionObjects = function() {
var results = arguments[0];
var lastArgument = arguments[arguments.length - 1];
var arrayCount = arguments.length;
var areEqualFunction;
//Internal function
var _intersection_of_2_Objects = function(array1, array2, areEqualFunction) {
var result = []
$.each(array1, function(indexArray1, valueArray1) {
$.each(array2, function(indexArray2, valueArray2) {
if (areEqualFunction(valueArray1, valueArray2)) {
result.push(valueArray2)
}
});
});
return result;
};
//
if (typeof lastArgument === "function") {
areEqualFunction = lastArgument;
arrayCount--;
}
for (var i = 1; i < arrayCount; i++) {
var array = arguments[i];
results = _intersection_of_2_Objects(results, array, areEqualFunction);
if (results.length === 0) {
break;
}
}
return results;
};
Call it like :
var _intersect = intersectionObjects(b, c, a, function(valueArray1, valueArray2) {
return (valueArray1.name == valueArray2.name);
});
console.log(_intersect);

Merge arrays in JS

Suppose I have the following arrays:
var first = [
{ id: 1, name: 'first' },
{ id: 2, name: 'second' },
{ id: 3, name: 'third' }
]
var second = [
{ id: 2, field: 'foo2' },
{ id: 3, field: 'foo3' },
{ id: 4, field: 'foo4' }
]
var third = [
{ id: 2, data: 'some2' },
{ id: 5, data: 'some5' },
{ id: 6, data: 'some6' }
]
I want to merge them to get the following result:
var result = [
{ id: 1, name: 'first', field: undefined, data: undefined },
{ id: 2, name: 'second', field: 'foo2', data: 'some2' },
{ id: 3, name: 'third', field: 'foo3', data: undefined },
{ id: 4, name: undefined, field: 'foo4', data: undefined },
{ id: 5, name: undefined, field: undefined, data: 'some5' },
{ id: 6, name: undefined, field: undefined, data: 'some6' }
]
How could I do it with JavaScript?
You should get all existed keys and after create new Objects with fill "empty" keys:
function mergeArrays(){
var keys = {};
//save all existed keys
for(var i=arguments.length;--i;){
for(var j=arguments[i].length;--j;){
for(var key in arguments[i][j]){
keys[key] = true;
}
}
}
var res = [];
for(var i=arguments.length;--i;){
for(var j=arguments[i].length;--j;){
//set clone of object
var clone = JSON.parse(JSON.stringify(arguments[i][j]));
for(var key in keys){
if(!(key in clone)){
clone[key] = undefined;
}
}
res.push(clone);
}
}
return res;
}
https://jsfiddle.net/x3b0tk3g/
There is no simple solution for what you want. Here is my suggestion.
var first = [
{ id: 1, name: 'first' },
{ id: 2, name: 'second' },
{ id: 3, name: 'third' }
]
var second = [
{ id: 2, filed: 'foo2' },
{ id: 3, field: 'foo3' },
{ id: 4, field: 'foo4' }
];
var third = [
{ id: 2, data: 'some2' },
{ id: 4, data: 'some4' },
{ id: 6, data: 'some6' }
];
var result = {};
first.concat(second,third).forEach(function(item){
var id = item.id;
var row = result[id];
if(!row){
result[id] = item;
return;
}
for(var column in item){
row[column] = item[column];
}
});
var finalResult = Object.keys(result).map(function(id){
return result[id];
});
console.log(finalResult);
fiddle: http://jsfiddle.net/bs20jvnj/2/
function getByProperty(arr, propName, propValue) {
for (var i = 0; i < arr.length; i++) {
if (arr[i][propName] == propValue) return arr[i];
}
}
var limit = first.length + second.length + third.length;
var res = [];
for (var i = 1; i < limit; i++) {
var x = $.extend({}, getByProperty(first, "id", i), getByProperty(second, "id", i), getByProperty(third, "id", i));
console.log(x["id"]);
if (x["id"] === undefined) x["id"] = i;
res.push(x);
}
console.log(res);
There's probably a shorter way to solve this, but this covers all the steps, including ensuring that there are default properties that are undefined if not found. It also takes any number of input arrays, and you can specify what default keys you require if they're not already covered by the keys in the existing objects, so pretty future-proof for your needs.
// merges the key/values of two objects
function merge(a, b) {
var key;
if (a && b) {
for (key in b) {
if (b.hasOwnProperty(key)) {
a[key] = b[key];
}
}
}
return a;
}
function concatenate() {
var result = [];
var args = arguments[0];
for (var i = 0, l = args.length; i < l; i++) {
result = result.concat(args[i]);
}
return result;
}
// return a default object
function getDefault() {
return {
id: undefined,
name: undefined,
data: undefined,
field: undefined
};
}
// loop over the array and check the id. Add the id as a key to
// a temporary pre-filled default object if the key
// doesn't exist, otherwise merge the existing object and the
// new object
function createMergedArray(result) {
var temp = {};
var out = [];
for (var i = 0, l = result.length; i < l; i++) {
var id = result[i].id;
if (!temp[id]) temp[id] = getDefault();
merge(temp[id], result[i]);
}
// loop over the temporary object pushing the values
// into an output array, and return the array
for (var p in temp) {
out.push(temp[p]);
}
return out;
}
function mergeAll() {
// first concatenate the objects into a single array
// and then return the results of merging that array
return createMergedArray(concatenate(arguments));
}
mergeAll(first, second, third);
DEMO

Using $.grep how to filter an array using another array as a filter source

I'm attempting to apply a filter to a JSON array using another array as my filter source. What would be the most efficient way to do that?
This is my code: my goal is to extract all elements of employees array where employee ids are the same as in employeeFilter array. Similar to SQL, where employeeId in (1,2).
var employees = [{
id: 1,
name: "john"
}, {
id: 2,
name: "paul"
}, {
id: 3,
name: "mary"
}];
var employeeFilter = [1, 2];
emps = $.grep(employees, function (value, i) {
for (j = 0; j < employeeFilter.length; j++) {
return (value.id == employeeFilter[j]); // not working
}
});
The most efficient would probably be to not use jQuery at all, but something like Array.filter
var employees = [{
id: 1,
name: "john"
}, {
id: 2,
name: "paul"
}, {
id: 3,
name: "mary"
}];
var employeeFilter = [1, 2];
var emps = employees.filter(function(employee) {
return employeeFilter.indexOf(employee.id) != -1;
});
FIDDLE
Its because you are always returning in first iteration of your employeeFilter, without ever matching to other values in the employeeFilter. A minor change, and it will work as required:
var employees = [{
id: 1,
name: "john"
}, {
id: 2,
name: "paul"
}, {
id: 3,
name: "mary"
}];
var employeeFilter = [1, 2];
emps = $.grep(employees, function (value) {
var result = false;
for (j = 0; j < employeeFilter.length; j++) {
result = (value.id == employeeFilter[j]);
if(result)
break;
}
return result;
});
demo:http://jsfiddle.net/ugze6dew/

Find objects in an array that are the same, not remove them

I'm trying to find objects in an array that are the same to flag them in the UI. I can't seem to use undescore to do it.
I was doing this:
var a = [ {'id': 1, 'name': 'jake' }, {'id':4, 'name': 'jenny'}, {'id': 9, 'name': 'nick'}, {'id': 1, 'name': 'jake' } ];
var eaches = _.each(a, function (obj) {
_.find(a, function () {
return _.isEqual(a, obj);
});
});
Thanks in advance!
Seems you need something like this:
var a = [{
'id': 1,
'name': 'jake'
}, {
'id': 4,
'name': 'jenny'
}, {
'id': 9,
'name': 'nick'
}, {
'id': 1,
'name': 'jake'
}];
var eq = [];
_.each(a, function (x, i) {
var e = _.find(a, function (y, j) {
return i !== j && _.isEqual(x, y);
});
if (e) {
eq.push(x);
}
});
console.log(eq);
http://jsfiddle.net/f0t0n/WBbs5/
UPDATE:
Custom "_.uniq" based on _.isEqual instead of === strict comparison:
var uniqEq = _.reject(eq, function(x, i) {
return _.find(eq, function(y, j) {
return i < j && _.isEqual(x, y);
});
});
http://jsfiddle.net/f0t0n/hzBBA/

Categories