Get the difference between two arrays of objects - javascript

I've got two arrays of objects, the difference between them is only that arrayAfter will have an element added:
var arrayBefore = [
{"name":"Alan","height":"171","weight":"66"},
{"name":"Ben","height":"182","weight":"90"}
];
var arrayAfter= [
{"name":"Alan","height":"171","weight":"66"},
{"name":"Ben","height":"182","weight":"90"},
{"name":"Chris","height":"163","weight":"71"}
];
"name" is always unique!
How can I find out which one is the element that has been added? I've tried ending up using nested for loops, but this seems overcomplicated.
I've also found the this nice idea:
var diff = $(arrayAfter).not(arrayBefore ).get();
However, that does not seem to work on arrays of objects straight ahead.
Is there some easy way to get the difference?

If only the name indicates uniqueness, you can do:
//Get a list of all the names in the before array
var beforeNames = arrayBefore.map(function(person) { return person.name });
//Filter the after array to only contain names not contained in the before array
var uniqueObjects = arrayAfter.filter(function(person) {
return beforeNames.indexOf(person.name) === -1;
});
console.log(uniqueObjects); //[{"name":"Chris","height":"163","weight":"71"}]
Demo: http://jsfiddle.net/tehgc8L5/

For a generic method you can combine Array.prototype.filter() with Array.prototype.reduce() which iterates over the object keys:
arrayAfter.filter(function(after) {
return !arrayBefore.reduce(function(found, before) {
if (!found) {
found = true;
for (key in before) {
if (before.hasOwnProperty(key)) {
found = found && (before[key] === after[key]);
}
}
}
return found;
}, false);
}); //[{name: "Chris", height: "163", weight: "71"}]

You can use Array.prototype.filter and filter out those elements in the previous array.
var differences = arrayAfter.filter(function(el) {
return arrayBefore.indexOf(el) === -1;
});

I believe jQuery will have nothing that will directly solve your problem here. Your problem being comparing objects for equality.
I am assuming that name is unique. If not, for this method you will need a unique identifier for data. If you absolute do not have one then you could concat all data to get one.
// first make a map of objects in before
var before = {};
arrayBefore.forEach(function(o){
before[o.name] = o;
});
// now we get the elements of after that do not exist in our hashmap
var result = arrayAfter.filter(function(o){
return !(o.name in before);
});
You can obviously wrap this up in a general function.

Related

Removing items from array in AngularJS

I have this two integers arrays:
I am working on my Angularjs tutorial project.
In controller I have this two arrays:
var arrA=[12,54,76,98,45];
var arrB=[12,98];
I need to delete from arrA all numbers that inside arrB.
arrA have to be like this after implementation:
arrA=[54,76,45]
What is best and elegantic way to implement it in angularjs?
You can use Array.prototype.filter() in conjunction with Array.prototype.indexOf()
The filter() method creates a new array with all elements that pass the test implemented by the provided function.
The indexOf() method returns the first index at which a given element can be found in the array, or -1 if it is not present.
var arrA=[12,54,76,98,45];
var arrB=[12,98];
arrA = arrA.filter(function(o){
return arrB.indexOf(o) == -1;
});
document.write(JSON.stringify(arrA));
Off the top of my head.
//Run a loop to go through all elements in arrB
for (var i=0;i<arrB.length;i++) {
//keep position of element i in arrA
//if it's not there index will be equal to -1
var index=arrA.indexOf(arrB[i])
//if it is there
if(index!=-1) {
//remove 1 element at position index from arrA
arrA.splice(index,1)
}
}
Good luck.
This has nothing to do with angular btw, it's basic javascript.
Here's a fiddle:
https://jsfiddle.net/MichaelSel/t2dfg31c/
how about the following:
var result = arrA.filter(function(elem){
return arrB.indexOf(elem) === -1;
);
To delete items from any array you need to use splice:
$scope.items.splice(index, 1);
now what you can do is, you can run a for loop to identify the duplicate element. Once identified you can remove it using splice function.
Angular doesn't concern itself with things like array manipulation. JavaScript provides facilities for that though:
var diff = arrA.filter(function(item) {
return arrB.indexOf(item) < 0;
});
Fiddle
If arrB is very large, you might want to allow it to be O(N) (for smallish ones) up to O(N log N), instead of O(n^2):
var lookup = arrB.reduce(function(lookup, item) {
lookup[item] = true;
return lookup;
}, {});
diff = arrA.filter(function(item) {
return !Object.prototype.hasOwnProperty.call(lookup, item);
});
However, this only works if the string representation of the item is what you are looking at. It would work for integers.

Javascript: Find douplicated values from array with keys

Title is pretty much self explanatory...
I want to be able to find duplicated values from JavaScript array.
The array keys can be duplicated so I need to validate only the array values.
Here is an example :
var arr=[
Ibanez: 'JoeSatriani',
Ibanez: 'SteveVai',
Fender: 'YngwieMalmsteen',
Fender: 'EricJohnson',
Gibson: 'EricJohnson',
Takamine: 'SteveVai'
];
In that example:
the key is the guitar brand
the value is the guitar player name.
So:
If there is duplicated keys (like: Ibanez or Fender) as on that current example that is OK :-)
But
If there is duplicated values (like: EricJohnson or SteveVai) I'm expecting to get (return) that error:
EricJohnson,SteveVai
You can't have associative arrays in Javascript. You can create an array of objects, like:
var arr=[
{Ibanez: 'JoeSatriani'},
{Ibanez: 'SteveVai'},
{Fender: 'YngwieMalmsteen'},
{Fender: 'EricJohnson'},
{Gibson: 'EricJohnson'},
{Takamine: 'SteveVai'}
];
Then you'll need a for...in loop to go over every object in the array, create a new array of values and check that for duplicates, which is also not very straightforward - basically you'll want to sort the array and make sure no value is the same as the one after it.
var arrayOfValues = [];
arr.forEach(function(obj){
for(var prop in obj)
arrayOfValues.push(obj[prop]);
});
arrayOfValues.sort(); // by default it will sort them alphabetically
arrayOfValues.forEach(function(element,index,array){
if(array[index+1] && element==array[index+1])
alert("Duplicate value found!");
});
First of all, object keys can not be repeated.
This means that:
({
"Fender": "Jim",
"Fender": "Bob"
})["Fender"]
Would simply return: "Bob".
However, I did make a code that could allow you to find duplicates in values, but as I said, the key will have to be unique:
var arr = {
Ibanez: 'EricJohnson',
Fender: 'YngwieMalmsteen',
Gibson: 'EricJohnson',
Takamine: 'SteveVai',
"Takamine2": 'SteveVai'
};
function contains(a, obj) {
for (var i = 0; i < a.length; i++) {
if (a[i] === obj) {
return true;
}
}
return false;
}
var track = [];
var exists = [];
for (var val in arr) {
if (contains(track, arr[val])) {
exists.push(arr[val]);
} else {
track.push(arr[val])
}
}
alert(exists)
You can see it working here: http://jsfiddle.net/dr09sga6/2/
As others have commented, the example array you provided isn't a valid JavaScript array. You could, however, keep a list for each guitar type:
var mapping = {
Ibanez: ['JoeSatriani','SteveVai'],
Fender: ['YngwieMalmsteen','EricJohnson']
Gibson: ['EricJohnson'],
Takamine: ['SteveVai']
];
Or a list of each guitar/musician pair:
var pairs = [
['Ibanez','JoeSatriani'],
['Ibanez','SteveVai'],
['Fender','YngwieMalmsteen'],
['Fender','EricJohnson'],
['Gibson','EricJohnson'],
['Takamine','SteveVai']
];
Your solution is going to depend on which pattern you go with. However, in the second case it can be done in one chained functional call:
pairs.map(function(e) {return e[1]}) // Discard the brand names
.sort() // Sort by artist
.reduce(function(p,c,i,a){
if (i>0 && a[i]==a[i-1] && !p.some(function(v) {return v == c;})) p.push(c);
return p;
},[]); //Return the artist names that are duplicated
http://jsfiddle.net/mkurqmqd/1/
To break that reduce call down a bit, here's the callback again:
function(p,c,i,a){
if (i>0
&& a[i]==a[i-1]
&& !p.some(function(v) {
return v == c;
}))
p.push(c);
return p;
}
reduce is going to call our callback for each element in the array, and it's going to pass the returned value for each call into the next call as the first parameter (p). It's useful for accumulating a list as you move across an array.
Because we're looking back at the previous item, we need to make sure we don't go out of bounds on item 0.
Then we're checking to see if this item matches the previous one in the (sorted) list.
Then we're checking (with Array.prototype.some()) whether the value we've found is ALREADY in our list of duplicates...to avoid having duplicate duplicates!
If all of those checks pass, we add the name to our list of duplicate values.

using underscore to compare two object lists and obtain the unique objects

We have 2 JavaScript object lists.
var AList = [{id:1,name:"AA"},{id:2,name:"BB"},{id:3,name:"CC"},{id:4,name:"DD"},{id:5,name:"EE"},{id:6,name:"FF"}]
var BList = [{id:1,name:"AA"},{id:2,name:"BB"},{id:3,name:"CC"},{id:4,name:"DD"}]
we need to eliminate the duplicates from both lists and return what is unique for AList. (id 5 and 6)
I've used a generic JavaScript implementation for this, but I like to implement a more sleek solution based on underscore.
for(var i=0;i<AList.length;i++){
for(var j=0;j<fBList.length;j++){
if(AList[i] && fBList[j] && (AList[i].id == BList[j].id)){
delete AList[i];
}
}
}
var uniqueList= _.uniq(AList);
After A list is done with deleting the duplicates, there are null elements in place where the duplicates were, therefore we needed to use _uniq to get a unique set of values.
_.difference(AList,BList)
Doesn't provide the answer.
You could combine the two arrays, then get the unique result.
var uniqueList = _.uniq(AList.concat(BList), function(item) {
return item.id;
});
Or you could use _.property(key):
var uniqueList = _.uniq(AList.concat(BList), _.property('id'));
Unfortunately _.difference does use strict equality, and there is no way to change that by a custom equality callback. You still need to compute it manually:
AList = _.uniq(AList, _.property('id'));
BList = _.uniq(BList, _.property('id'));
var bIds = _.pluck(BList, "id");
_.filter(AList, function(el) { return !_.contains(bIds, el.id); })
For those who stumble on this as I did, lodash now has a function called differenceWith which takes a comparator.
_.differenceWith(array, [values], [comparator])
https://lodash.com/docs#differenceWith

Most efficient and clean way to push a value to an array only if it does not exist yet

Suppose an array named myArray containing several values but no duplicates.
Suppose I want to push a value into it only if it won't lead to duplicates presence.
How I determinate duplicates => by comparing value's id.
I thought about Lodash#uniq to do the trick:
myArray.push(aNewValue);
myArray = _.uniq(myArray,function(item){
return item.id;
});
However, I don't like the reassignment to the array and especially the fact that I have to push before checking...
Is there a more "functional" way to achieve it while being very short?
I don't want to iterate through the array explicitly in order to apply the check.
That's why I attempted to use Lodash.
You can check the presence of an item before adding it :
if(myArray.indexOf(aNewValue) == -1) {
myArray.push(aNewValue);
}
The most efficient way to do this is generally to use an object for uniqueness, because an object can have at most one key of a certain value. However, this is restricted to strings and things that stringify, since only strings can be object keys.
There are two approaches here. If you are using your array often, then you should keep parallel structures - an object for uniqueness check, an array for arrayness of it.
If you don't need your array often, i.e. you want to push a bunch of things and then have an array be unique, you can just use the object, and transform it into an array when you need it (which is somewhat expensive, so you only want to do it once, but still cheaper than manipulating two different structures all the time).
The first approach is illustrated here:
function Set() {
this.presence = {};
this.array = [];
};
Set.prototype.push = function(key, value) {
if (this.presence[key]) return;
this.presence[key] = true;
this.array.push(value);
};
var a = new Set();
a.push(3, { id: 3, value: "SOMETHING" });
a.push(7, { id: 7, value: "SOMETHING ELSE" });
a.push(3, { id: 3, value: "SOMETHING" });
console.log(a.array); // => only 2 elements
The second, here:
function Set() {
this.store = {};
};
Set.prototype.push = function(key, value) {
this.store[key] = value;
};
Set.prototype.array = function() {
var that = this;
return Object.keys(this.store).map(function(key) { return that.store[key]; })
};
...
console.log(a.array()); // note the newly added parentheses :)
Both of these are still cheaper than looking for presence inside the array using indexOf, even more so when you do your own iterating, except very much maybe in case the array is very short.
You could use Array.prototype.some() to find out if the value is already part of the array, e.g.:
if( myArray.some(function (elem) { return elem.id == newValue.id }) )
myArray.push(newValue);
You can't really get away with not looping through the array, though.

How to filter Meteor database query results?

I'm having so much trouble finding an elegant way of filtering by mongodb query results against an array of objects I dont want.
I get an array of objects:
var articles = Tips.find().fetch();
And I have a few articles that have already been selected and should be returned
var selected = [{Object}, {Object}];
I find it hard to believe that theres no built in function such as:
articles.remove(selected);
But there isn't, and given the amount we're working with MongoDb in Meteor, I figured someone has already found some good helper functions for doing this and other similar functionality
Thanks
So I found a reasonable solution, but its incomplete:
Array.prototype.removeObjWithValue = function(name, value){
var array = $.map(this, function(v,i){
return v[name] === value ? null : v;
});
this.length = 0; //clear original array
this.push.apply(this, array); //push all elements except the one we want to delete
}
Array.prototype.removeObj = function(obj){
var array = $.map(this, function(v,i){
return v["_id"] === obj["_id"] ? null : v;
});
this.length = 0; //clear original array
this.push.apply(this, array); //push all elements except the one we want to delete
}
The problem I am still running into is that this doesnt work and keep returning []
Array.prototype.removeObjs = function(objs){
var array = this;
console.log(array);
$.each(objs, function (i,v) {
array.removeObj(v);
console.log(array);
})
console.log(array);
this.length = 0; //clear original array
this.push.apply(this, array); //push all elements except the ones we want to delete
}
As you suggested in your comment, I believe $nin is what you want (yes, it's available in meteor). For example:
var selectedIds = _.pluck(selected, _id);
var articles = Tips.find({_id: {$nin: selectedIds}});
This is also nice if you are running it on the client because you don't have to call fetch prior to rendering.
Using underscore.js (present by default in Meteor), try
_.difference(articles, [{Object}, {Object}]);
Source: http://underscorejs.org/#difference

Categories

Resources