Compare array objects and show difference - javascript

I have two arrays which I want to compare and check if there is an deleted item in one of these arrays. If there is show me the difference (deleted item)
Here is the code below how I would like to achieve this:
var completedList = [{id:1},{id:2},{id:3},{id:4},{id:7},{id:8}];
var invalidList = [{id:3},{id:4},{id:5},{id:6}];
// filter the items from the invalid list, out of the complete list
var validList = completedList.map((item) => {
console.log(item.id)
return item.id;
//console.log(invalidList.id);
}).filter(item => {
Object.keys(invalidList).map(key => {
console.log(invalidList[key].id)
//return !invalidList[key].id.includes(item.id);
});
})
console.log(validList); // Print [1,2,7,8]
// get a Set of the distinct, valid items
var validItems = new Set(validList);
But this returns me a lot of id's how can I map through both array's and filter on object property id? And only show the difference between these array objects.
So basically what I expect is to see the difference between those arrays so log the differences in id's so in this example: 1,2,5,6,7,8

You could take a Set for getting a difference. For getting the differences from each other (a symmetric difference), you need to get both differences.
const
difference = (a, b) => Array.from(b.reduce((s, v) => (s.delete(v), s), new Set(a))),
getId = ({ id }) => id;
var completedList = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 7 }, { id: 8 }],
invalidList = [{ id: 3 }, { id: 4 }, { id: 5 }, { id: 6 }],
complete = completedList.map(getId),
invalid = invalidList.map(getId),
left = difference(complete, invalid),
right = difference(invalid, complete),
result = [...left, ...right]
console.log(result.join(' '));
console.log(left.join(' '));
console.log(right.join(' '));

This should do the trick.
let completedList = [{id:1},{id:2},{id:3},{id:4},{id:7},{id:8}];
let invalidList = [{id:3},{id:4},{id:5},{id:6}];
// filter the items from the invalid list, out of the complete list
let temp1 = completedList.map(e => e.id);
let temp2 = invalidList.map(e => e.id);
let validList = temp1.filter(e => temp2.indexOf(e) === -1);
// find items only in invalidList
let difference = temp2.filter(e => temp1.indexOf(e) === -1);
console.log(validList); // Print [1,2,7,8]
console.log(difference);

I often rely on lodash implementation for comparison.
In lo dash you can get the job done following manner
_.intersectionWith(arr1, arr2, _.isEqual) - For similarity
_.differenceWith(arr1, arr2, _.isEqual) - for differences
This ans is confined to using a util library to get the job done.
If you are looking for the exact algo I would definitely take some time to develop it and reply as a comment to this post .
Thanks

var completedList = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 7 }, { id: 8 }];
var invalidList = [{ id: 3 }, { id: 4 }, { id: 5 }, { id: 6 }];
//get the items that are in the invalid list but not completed list
var filteredList1 = invalidList.filter((invalidListItem) => !completedList.find((item) => item.id === invalidListItem.id));
//get the items that are in the completed list but not in the invalid list
var filteredList2 = completedList.filter((completedListItem) => !invalidList.find((item) => item.id === completedListItem.id));
//join the two arrays
var difference = filteredList1.concat(filteredList2);
//display the merged array and sort
console.log(difference.sort((item1, item2) => { return item1.id > item2.id ? 1 : item1.id < item2.id ? -1 : 0; }));
//outputs 1,2,5,6,7,8

Related

find duplicate values in array of objects in javascript

I have two lists in javascript that are of same structure like below:
var required_documents = [{"id":1,"dt":1},{"id":2,"dt":2},{"id":3,"dt":3}];
var existing_documents = [{"id":1,"dt":1},{"id":2,"dt":2},{"id":3,"dt":4}];
I need to remove all records from database that are in existing documents list (i.e "dt") but NOT in required_documents list.
For the above scenario I should remove only {"id":3,"dt":4} and insert {"id":3,"dt":3}. I am not sure how I can compare on just one property. This is below that I found on SOF sometime ago but can't find it again apologies for not referencing it.
required_documents.forEach((obj) => {
const elementInArr2 = existing_documents.find((o) => o.dt === obj.dt);
console.log('found elementinarr: ' + obj.dt);
});
This returns unique objects like dt:1,dt:2,dt:3 but I need dt:4 from the existing documents list as it is the one that is not in the required documents list and needs to be deleted. How can I get just the one that is not in the required documents list.
Assuming both id and dt properties are significant, I would first create a means of hashing an entry and then build a hashed set of required_documents.
Then you can filter out anything from existing_documents that is in the set, leaving only the results you want.
const required_documents = [{"id":1,"dt":1},{"id":2,"dt":2},{"id":3,"dt":3}];
const existing_documents = [{"id":1,"dt":1},{"id":2,"dt":2},{"id":3,"dt":4}];
// a simple stringify hash
const createHash = ({ id, dt }) => JSON.stringify({ id, dt });
const requiredHashSet = new Set(required_documents.map(createHash));
const result = existing_documents.filter(
(doc) => !requiredHashSet.has(createHash(doc))
);
console.log(result);
The hash creation can be anything that produces a comparable entity that can uniquely identify a record.
You need to run it twice to confirm there is no elements left in existing. So create a function and use it.
var required_documents = [{"id":1,"dt":1},{"id":2,"dt":2},{"id":3,"dt":3}];
var existing_documents = [{"id":1,"dt":1},{"id":2,"dt":2},{"id":3,"dt":4}]
let output = [];
output = output.concat(extractUniqueValues(required_documents, output));
output = output.concat(extractUniqueValues(existing_documents, output));
console.log(output)
function extractUniqueValues(input, output){
return input.filter((item)=>{
return !output.find(v => v.dt == item.dt)
})
}
You can do like below
var required_documents = [
{ id: 1, dt: 1 },
{ id: 2, dt: 2 },
{ id: 3, dt: 3 },
];
var existing_documents = [
{ id: 1, dt: 1 },
{ id: 2, dt: 2 },
{ id: 3, dt: 4 },
];
for (let index = 0; index < required_documents.length; index++) {
const element = required_documents[index];
for (var i = existing_documents.length - 1; i >= 0; i--) {
const child = existing_documents[i];
if (element.id === child.id && element.dt === child.dt) {
existing_documents.splice(i, 1);
} else {
required_documents.push(element);
}
}
}
LOG not exist [{"dt": 4, "id": 3}]
LOG unique items [{"dt": 1, "id": 1}, {"dt": 2, "id": 2}, {"dt": 3, "id": 3}]
If you don't care about time complexity, something this should work:
var new_documents = existing_documents.filter(ed => {
return required_documents.find(rd => rd.dt == ed.dt);
});
Edit Okay, I just reread your question and I'm a bit confused. Do you want the object {id: 3, dt: 3} inside the new array as well?

JS - Filter array of objects returning only a specific field (some kind of combination between map and filter)

I have the following array
const arr = [
{ id: 1, token: "aAdsDDwEwe43svdwe2Xua" },
{ id: 2, token: undefined }
];
And I need to filter out undefined tokens, and ignore the id field.
Something like:
const arr = [
{ id: 1, token: "aAdsDDwEwe43svdwe2Xua" },
{ id: 2, token: undefined },
];
const result = arr
.filter(({ token }) => token !== undefined)
.map(({ token }) => token);
console.log(result);
Is it possible to do it in O(n) ? I mean, without navigating through the list twice.
const result = arr.reduce((acc,curr) => {
return curr.token !==undefined ? [...acc,curr.token] : acc
},[])
Firstly it is O(n). Just because we run over a loop two times, it does not become O(n^2).
Additionally, if you simply use a for loop you will realise how simple it is:
const arr = [
{ id: 1, token: "aAdsDDwEwe43svdwe2Xua", extraField : "x" },
{ id: 2, token: undefined, extraField : "x" }
];
let ans = [];
for(let i = 0 ; i < arr.length; i++){
if(arr[i].token!== undefined){
let { id, ...newBody } = arr[i];
ans.push(newBody);
}
}
console.log(ans);
Used spread operator (...), to remove a particular property (here id).
If you are looking for array methods, the above could also be achieved using a .forEach()

Removing duplicates with in an object array using angular 4

I have an array with below list of items as shown in image , I would like to remove the duplicates
[L7-LO, %L7-LO] from that array.
I have tried with the following conditions:
Scenario 1 :
this.formulalist.filter((el, i, a) => i == a.indexOf(el))
Scenario 2:
Observable.merge(this.formulalist).distinct((x) => x.Value)
.subscribe(y => {
this.formulalist.push(y)
});
Scenario 3:
this.formulalist.forEach((item, index) => {
if (index !== this.formulalist.findIndex(i => i.Value == item.Value))
{
this.formulalist.splice(index, 1);
}
});
None of the three scenarios above were able to remove the duplicates from that array. Could any one please help on this query?
angular is not necessary use vanillajs
filter the elements with only one occurrence and add to the new list the first occurrence
let newFormulalist = formulalist.filter((v,i) => formulalist.findIndex(item => item.value == v.value) === i);
Try populating a new array without duplicates. Assign the new array later to formulalist.
newArr = []
this.formulalist.forEach((item, index) => {
if (this.newArr.findIndex(i => i.Value == item.Value) === -1)
{
this.newArr.push(item)
}
});
this.formulalist = this.newArr
EDIT
Looking at the answer above, the solution seems so outdated. A better approach would have been to use an Array.filter() than a Array.forEach().
But, having a better solution would be nice, now when I see this question, I feel findIndex() not to be a good approach because of the extra traversal.
I may have a Set and store the values in the Set on which I want to filter, If the Set has those entries, I would skip those elements from the array.
Or a nicer approach is the one that is used by Akitha_MJ, very concise. One loop for the array length, an Object(Map) in the loop with keys being the value on which we want to remove duplicates and the values being the full Object(Array element) itself. On the repetition of the element in the loop, the element would be simply replaced in the Map. Later just take out the values from the Map.
const result = Array.from(this.item.reduce((m, t) => m.set(t.name, t), new Map()).values());
Hope this works !!
// user reduce method to remove duplicates from object array , very easily
this.formulalist= this.formulalist.reduce((a, b) => {
if (!a.find(data => data.name === b.name)) {
a.push(b);
}
return a;
}, []);
// o/p = in formulalist you will find only unique values
Use a reducer returning a new array of the unique objects:
const input = [{
value: 'L7-LO',
name: 'L7-LO'
},
{
value: '%L7-LO',
name: '%L7-LO'
},
{
value: 'L7-LO',
name: 'L7-LO'
},
{
value: '%L7-LO',
name: '%L7-LO'
},
{
value: 'L7-L3',
name: 'L7-L3'
},
{
value: '%L7-L3',
name: '%L7-L3'
},
{
value: 'LO-L3',
name: 'LO-L3'
},
{
value: '%LO-L3',
name: '%LO-L3'
}
];
console.log(input.reduce((acc, val) => {
if (!acc.find(el => el.value === val.value)) {
acc.push(val);
}
return acc;
}, []));
if you are working using ES6 and up, basic JS using map and filter functions makes it easy.
var array = [{value:"a"},{value:"b"},{value:"c"},{value:"a"},{value:"c"},{value:"d"}];
console.log(array.filter((obj, pos, arr) => {
return arr.map(mapObj => mapObj["value"]).indexOf(obj["value"]) === pos;
}));
Filtering for unique values is much faster with assigning values to some object properties - there not will be duplicates.
This approach gets better and better with every +1 member of initial array, because looping will be causing fast algorithm complications
let arr = [
{value: 'L7-LO', name: 'L7-LO'},
{value: '%L7-LO', name: '%L7-LO'},
{value: 'L7-LO', name: 'L7-LO'},
{value: '%L7-LO', name: '%L7-LO'},
{value: 'L7-L3', name: 'L7-L3'},
{value: '%L7-L3', name: '%L7-L3'},
{value: 'LO-L3', name: 'LO-L3'},
{value: '%LO-L3', name: '%LO-L3'}
];
let obj = {};
const unique = () => {
let result = [];
arr.forEach((item, i) => {
obj[item['value']] = i;
});
for (let key in obj) {
let index = obj[key];
result.push(arr[index])
}
return result;
}
arr = unique(); // for example;
console.log(arr);

decrease count value for array inside objects and count once reach zero automatically remove that object using java script

i have one object and its having service array i checked if any service id and offer id match with object its decrease count value and once count reach zero need to remove that object.please check my try in fiddle. i can able to reduce but while reaching zero not able to remove
const obj = {
name:'saloon',
services:[
{
sid:1,
offid:20,
count:2
},
{
sid:2,
offid:18,
count:1
},
{
sid:3,
offid:15,
count:3
}
]
}
given values : based on this service and offer id count should decrease once count reach zero need to remove that object
const servid = 2
const offid = 18
mycode
obj.services = obj.services.map(data => {
if(data.sid == servid && data.offid == offid ){
return data.count > 1 && {
sid:data.sid,
offide:data.offid,
count:data.count -1
}
}else{
return data
}
})
console.log(obj);
expected result :
const result = {
name:'saloon',
services:[
{
sid:1,
offid:20,
count:2
},
{
sid:3,
offid:15,
count:3
}
]
}
Use array#forEach to iterate through your array and check each sid and offid inside service, in case of match update the count value after that check if count value is less than or equal to zero, if it is, then using push its index into indexes array. After that, you can iterate through indexes array and delete those values in services array using array#splice.
const obj = { name:'saloon', services:[ { sid:1, offid:20, count:2 }, { sid:2, offid:18, count:1 }, { sid:3, offid:15, count:3 } ] };
const servid = 2;
const offid = 18;
var indexes = [];
obj.services.forEach(function(service, index) {
if(service.sid === servid && service.offid === offid){
--service.count;
}
if(service.count <= 0)
indexes.push(index);
});
indexes.reverse().forEach(function(index){
obj.services.splice(index, 1);
});
console.log(obj);
Use .forEach() to decrease the count, then .filter() to remove elements with count == 0:
const obj = {
name: 'saloon',
services: [{
sid: 1,
offid: 20,
count: 2
}, {
sid: 2,
offid: 18,
count: 1
}, {
sid: 3,
offid: 15,
count: 3
}]
}
const servid = 2
const offid = 18
obj.services.forEach((data, i) => {
if (data.sid == servid && data.offid == offid) {
data.count--;
}
})
obj.services = obj.services.filter(data => data.count > 0);
console.log(obj);

javascript find child object in nested arrays

I have a javascript structure like below (nested arrays of objects)
var categoryGroups = [
{
Id: 1, Categories: [
{ Id: 1 },
{ Id: 2 },
]
},
{
Id: 2, Categories: [
{ Id: 100 },
{ Id: 200 },
]
}
]
I want to find a child Category object matching an Id, assuming the Category Id's are all unique.
I've got this below, but was wondering if there is a more concise way of doing it:
var category, categoryGroup, found = false;
for (i = 0; i < categoryGroups.length ; i++) {
categoryGroup = categoryGroups[i];
for (j = 0; j < categoryGroup.Categories.length; j++) {
category = categoryGroup.Categories[j];
if (category.Id === id) {
found = true;
break;
}
}
if (found) break;
}
Using flatMap in ES2019
const category = categoryGroups.flatMap(cg => cg.Categories).find(c => c.Id === categoryId);
Caveat: This uses a couple of Array.prototype functions that were only added in ECMAScript 5 and thus will not work with older browsers unless you polyfill them.
You can loop over all first-level objects in your array, and then filter the categories based on your condition and collect all matches in an array. Your final result will be the first element in the array of matches (no match found if array is empty).
var matches = [];
var needle = 100; // what to look for
arr.forEach(function(e) {
matches = matches.concat(e.Categories.filter(function(c) {
return (c.Id === needle);
}));
});
console.log(matches[0] || "Not found");
JSFiddle: http://jsfiddle.net/b7ktf/1/
References:
Array.prototype.forEach
Array.prototype.concat
Array.prototype.filter
Using only Array.prototype.filter():
If you are sure that the id you are looking for exists, you can do:
var id = 200; // surely it exists
var category = arr.filter(g => g.Categories.filter(c => c.Id === id)[0])[0].Categories.filter(c => c.Id === id)[0];
If you are not sure that it exists:
var id = 201; // maybe it doesn't exist
var categoryGroup = arr.filter(e => e.Categories.filter(c => c.Id === id)[0])[0];
var category = categoryGroup ? categoryGroup.Categories.filter(c => c.Id === id)[0] : null;
jsfiddle
Using reduce and recursion :
function nestedSearch(value) {
return categoryGroups.reduce(function f(acc, val) {
return (val.Id === value) ? val :
(val.Categories && val.Categories.length) ? val.Categories.reduce(f, acc) : acc;
});
}
> try on JSFiddle
check the code in the fiddle
var categoryGroups = [
{
Id: 1, Categories: [
{ Id: 1 },
{ Id: 2 },
]
},
{
Id: 2, Categories: [
{ Id: 100 },
{ Id: 200 },
]
}
]
var id = 100;
var x = 'not found';
var category, categoryGroup, found = false;
for (i = 0; i < categoryGroups.length ; i++) {
categoryGroup = categoryGroups[i];
for (j = 0; j < categoryGroup.Categories.length; j++) {
category = categoryGroup.Categories[j];
if (category.Id == id) {
var x = category.Id;
found = true;
break;
}
}
if (found) break;
}
alert(x);
The above code checks if id = 100 is found in the array. If found will alert the value else alerts that its not found. value '100' has been hardcoded for the sake of demo
You could wrap it inside a function to get rid of the awkward break; syntax and you can load each element into a variable inside the for(;;) construct to shave off a few lines.
function subCategoryExists(groups, id)
{
for (var i = 0, group; group = groups[i]; ++i) {
for (var k = 0, category; category = group.Categories[k]; ++k) {
if (category.Id == id) {
return true;
}
}
}
return false;
}
var found = subCategoryExists(categoryGroups, 100);
Easy way using lodash library of NodeJS (assuming you are using NodeJS):
const _ = require('lodash');
let category ;
let categoryGroup = _.find(categoryGroups, (element)=>{
category = _.find(element.Categories, {Id : 100});
return category;
});
console.log(categoryGroup); // The category group which has the sub category you are looking for
console.log(category); // The exact category you are looking for
If you want to actually return the inner category (instead of just checking for it's presence) you can use reduce:
return categoryGroups.reduce((prev, curr) => {
//for each group: if we already found the category, we return that. otherwise we try to find it within this group
return prev || curr.Categories.find(category => category.Id === id);
}, undefined);
This short-circuits on the inner categories, and touches each categoryGroup once. It could be modified to short-cicuit on the categoryGroups as well.
Here's a JS Fiddle demonstration.
You could use underscore:
var cat = _(categoryGroups).
chain().
pluck('Categories').
flatten().
findWhere({Id: 2}).
value();
What I'm doing here is that I'm extracting all Categories values in a single array and then grepping for the correct categories.
EDIT: sorry, didn't get your question right the first time. As the comments suggest, you might not want to use underscore just for that, but that's how I would do it :)
We are using object-scan for our data processing now. It's very powerful once you wrap your head around it. For your questions this would look like this:
// const objectScan = require('object-scan');
const lookup = (id, data) => objectScan(['Categories.Id'], {
useArraySelector: false,
abort: true,
rtn: 'parent',
filterFn: ({ value }) => value === id
})(data);
const categoryGroups = [{ Id: 1, Categories: [{ Id: 1 }, { Id: 2 }] }, { Id: 2, Categories: [{ Id: 100 }, { Id: 200 }] }];
console.log(lookup(1, categoryGroups));
// => { Id: 1 }
console.log(lookup(100, categoryGroups));
// => { Id: 100 }
console.log(lookup(999, categoryGroups));
// => undefined
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan

Categories

Resources