compare two array of objects and get difference - javascript

Mock
a = [{id:123},{id:1234},{id:12345}]
b = [{id:123},{id:1234},{id:123456}]
Code
a.filter((element)=> {
return b.some((ele) =>{
if (element.id === ele.id) {
return matched[element.id] = element.id
} else {
return unmatched[element.id] = element.id
}
});
});
Expected output
matched = {123: 123, 1234: 1234}
unmatched = {12345: 12345}
output
unmatched = {123: 123, 1234: 1234, 12345: 12345}
matched = {123: 123, 1234: 1234}
could any one help me out here. I am trying to compare two arrays and get the difference into different objects

You could take a Set or object for a and iterate b for getting the poperty into the right object.
const
a = [{ id: 123 }, { id: 1234 }, { id: 12345 }],
b = [{ id: 123 }, { id: 1234 }, { id: 123456 }],
aSet = new Set(a.map(({ id }) => id)),
[matched, unmatched] = b.reduce((r, { id }) => {
Object.assign(r[1 - aSet.has(id)], { [id]: id });
return r;
}, [{}, {}]);
console.log(matched);
console.log(unmatched);
.as-console-wrapper { max-height: 100% !important; top: 0; }
An approach by using the objects directly
const
a = [{ _id: '123', index: 3 }, { _id: '1234', index: 3 }],
b = [{ _id: '123', index: 2 }, { _id: '12345', index: 3 }],
aSet = new Set(a.map(({ _id }) => _id)),
[matched, unmatched] = b.reduce((r, o) => {
r[1 - aSet.has(o._id)].push(o);
return r;
}, [[], []]);
console.log(matched);
console.log(unmatched);
.as-console-wrapper { max-height: 100% !important; top: 0; }

This would work:
a = [{id:123},{id:1234},{id:12345}];
b = [{id:123},{id:1234},{id:123456}];
function compare(a, b) {
const returnObj = { matched: [], unmatched: [] };
a.forEach(aItem => {
const found = b.find(bItem => JSON.stringify(aItem) === JSON.stringify(bItem));
returnObj[found ? 'matched' : 'unmatched'].push(aItem);
});
return returnObj;
}
const { matched, unmatched } = compare(a, b);
console.log(matched);
console.log(unmatched);

Related

Return similar values ​from multiple array of objects in Javascript

Guys I made a simple example to illustrate my problem. I have 3 object arrays, datasOne, datasTwo and datasThree and what I want is to return a new array only with the objects that are in the 3 arrays. For example, if there is only Gustavo in the 3 arrays, then he will be returned. But there is a detail that if the datasThree is an empty array, then it will bring the data in common only from datasOne and datasTwo and if only the datasTwo which has data and the other two arrays have empty, then it will return data only from datasTwo. In other words it is to return similar data only from arrays that have data. I managed to do this algorithm and it works the way I want, but I would like to know another way to make it less verbose and maybe simpler and also work in case I add more arrays to compare like a dataFour for example. I appreciate anyone who can help me.
My code below:
let datasOne = [
{ id: 1, name: 'Gustavo' },
{ id: 2, name: 'Ana' },
{ id: 3, name: 'Luiz' },
{ id: 8, name: 'Alice' }
]
let datasTwo = [
{ id: 1, name: 'Gustavo' },
{ id: 3, name: 'Luiz' },
{ id: 8, name: 'Alice' }
]
let datasThree = [
{ id: 1, name: 'Gustavo' },
{ id: 3, name: 'Luiz' },
{ id: 2, name: 'Ana' },
{ id: 5, name: 'Kelly' },
{ id: 4, name: 'David' }
]
let filtered
if (datasOne.length > 0 && datasTwo.length > 0 && datasThree.length > 0) {
filtered = datasOne.filter(firstData => {
let f1 = datasThree.filter(
secondData => firstData.id === secondData.id
).length
let f2 = datasTwo.filter(
secondData => firstData.id === secondData.id
).length
if (f1 && f2) {
return true
}
})
} else if (datasOne.length > 0 && datasTwo.length > 0) {
filtered = datasOne.filter(firstData => {
return datasTwo.filter(secondData => firstData.id === secondData.id).length
})
} else if (datasOne.length > 0 && datasThree.length > 0) {
filtered = datasOne.filter(firstData => {
return datasThree.filter(secondData => firstData.id === secondData.id)
.length
})
} else if (datasTwo.length > 0 && datasThree.length > 0) {
filtered = datasTwo.filter(firstData => {
return datasThree.filter(secondData => firstData.id === secondData.id)
.length
})
} else if (datasThree.length > 0) {
filtered = datasThree
} else if (datasTwo.length > 0) {
filtered = datasTwo
} else if (datasOne.length) {
filtered = datasOne
}
console.log(filtered)
1) You can first filter the array which is not empty in arrs.
const arrs = [datasOne, datasTwo, datasThree].filter((a) => a.length);
2) Flatten the arrs array using flat().
arrs.flat()
3) Loop over the flatten array and count the occurrence of all objects using Map
const map = new Map();
for (let o of arrs.flat()) {
map.has(o.id)
? (map.get(o.id).count += 1)
: map.set(o.id, { ...o, count: 1 });
}
4) Loop over the map and collect the result only if it is equal to arrs.length
if (count === arrs.length) result.push(rest);
let datasOne = [
{ id: 1, name: "Gustavo" },
{ id: 2, name: "Ana" },
{ id: 3, name: "Luiz" },
{ id: 8, name: "Alice" },
];
let datasTwo = [
{ id: 1, name: "Gustavo" },
{ id: 3, name: "Luiz" },
{ id: 8, name: "Alice" },
];
let datasThree = [
{ id: 1, name: "Gustavo" },
{ id: 3, name: "Luiz" },
{ id: 2, name: "Ana" },
{ id: 5, name: "Kelly" },
{ id: 4, name: "David" },
];
const arrs = [datasOne, datasTwo, datasThree].filter((a) => a.length);
const map = new Map();
for (let o of arrs.flat()) {
map.has(o.id)
? (map.get(o.id).count += 1)
: map.set(o.id, { ...o, count: 1 });
}
const result = [];
for (let [, obj] of map) {
const { count, ...rest } = obj;
if (count === arrs.length) result.push(rest);
}
console.log(result);
/* This is not a part of answer. It is just to give the output fill height. So IGNORE IT */
.as-console-wrapper { max-height: 100% !important; top: 0; }
Not 100% sure it cover all edge cases, but this might get you on the right track:
function filterArrays(...args) {
const arraysWithData = args.filter((array) => array.length > 0);
const [firstArray, ...otherArrays] = arraysWithData;
return firstArray.filter((item) => {
for (const array of otherArrays) {
if (!array.some((itemTwo) => itemTwo.id === item.id)) {
return false;
}
}
return true;
});
}
Usage:
const filtered = filterArrays(datasOne, datasTwo, datasThree);
console.log(filtered)
I believe the code is fairly readable, but if something is not clear I'm glad to clarify.
function merge(arr){
arr = arr.filter(item=>item.length>0)
const map = {};
arr.forEach(item=>{
item.forEach(obj=>{
if(!map[obj.id]){
map[obj.id]=[0,obj];
}
map[obj.id][0]++;
})
})
const len = arr.length;
const ret = [];
Object.keys(map).forEach(item=>{
if(map[item][0]===len){
ret.push(map[item][1])
}
})
return ret;
}
merge([datasOne,datasTwo,datasThree])

Getting occurrences of different values on nested object

I've an array of objects like this:
arrObj = [{
id: 1
data: {
info: {
name: 'jhon'
}
}
},{
id: 1
data: {
info: {
name: 'jane'
}
}
},{
id: 1
data: {
info: {
name: 'jhon'
}
}
}]
And I needs get a summary of occurrences for different values, like this:
{ jane: 1, jhon: 2 }
The big problem is that I need pass the nested prop dynamically:
getSummary('data.info.name',obj) //--> { jane: 1, jhon: 2 }
Any ideas?
You can use the below code, this is just hint. you need to do error handling if some input is not having correct nested keys.
let arrObj = [{
id: 1,
data: {
info: {
name: 'jhon'
}
}
},{
id: 1,
data: {
info: {
name: 'jane'
}
}
},{
id: 1,
data: {
info: {
name: 'jhon'
}
}
}]
const getSummary = (dynamicKeys,obj) => {
const list = dynamicKeys.split('.');
const op = {};
for (let i = 0; i < obj.length; i++) {
let n = 1, key = obj[i][list[0]];
while (list.length > n) {
key = key[list[n]];
n++;
}
op[key] = op[key] ? op[key] + 1 : 1;
}
return op;
}
const test = getSummary('data.info.name', arrObj);
console.log(test)
A possible solution could be as below. Here at first given prop is found out from each element of arrayObj. If the finding isn't successful, the element is skipped and move to next. When the finding is successful, append the finding value to summary if it does not exist in summary or increment the existing value. You can change the code as your requirements.
const arrObj = [{
id: 1,
data: {
info: {
name: 'jhon'
}
}
}, {
id: 1,
data: {
info: {
name: 'jane'
}
}
}, {
id: 1,
data: {
info: {
name: 'jhon'
}
}
}];
const getSummary = (prop, arr) => {
const keys = prop.split('.');
const findPropValue = (elem) =>
keys.reduce((val, key, index) => {
if (index === 0) return elem[key];
return (val && val[key]) || val
}, null);
return arr.reduce((sum, curr) => {
const key = findPropValue(curr);
if (!key) return sum;
sum[key] = (sum[key] && sum[key] + 1) || 1;
return sum;
}, {});
};
console.log(getSummary('data.info.name', arrObj));
Go over elements using forEach. For each object, access the value and build a res object with keys as value (eg jane) and object values are aggregated.
[Access the value, by split the path, access object nested using reduce)
const getSummary = (path, items) => {
const paths = path.split(".");
const res = {};
items.forEach((item) => {
const value = paths.reduce((acc, cur) => acc[cur], item);
res[value] = (res[value] ?? 0) + 1;
});
return res;
};
arrObj = [
{
id: 1,
data: {
info: {
name: "jhon",
},
},
},
{
id: 1,
data: {
info: {
name: "jane",
},
},
},
{
id: 1,
data: {
info: {
name: "jhon",
},
},
},
];
const output = getSummary("data.info.name", arrObj);
console.log(output);

How to remove object in array of objects with matching values

I want to check my array for objects with matching values, if they match remove the object with the lowest index as that will be the one is "older"
I had success using this method for removing duplicate objects in the array, but when i get to specific values of those objects i'm not sure
someFunction() {
let cart = this.state.currentUser.cart
const newCartArray = cart.filter((light, index) => {
return index === cart.findIndex(obj => {
obj.use === light.use
})
})
cart = newCartArray
}
You could take a Map and store the last object with a wanted key and get as result only the last stored objects.
var array = [{ id: 1, index: 0 }, { id: 2, index: 1 }, { id: 3, index: 2 }, { id: 2, index: 3 }, { id: 3, index: 4 }, { id: 1, index: 5 }, { id: 4, index: 6 }, { id: 5, index: 7 }],
result = Array.from(array.reduce((m, o) => m.set(o.id, o), new Map).values());
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
If you like to keep the original order, you could check the same object reference for filtering.
var array = [{ id: 1, index: 0 }, { id: 2, index: 1 }, { id: 3, index: 2 }, { id: 2, index: 3 }, { id: 3, index: 4 }, { id: 1, index: 5 }, { id: 4, index: 6 }, { id: 5, index: 7 }],
map = array.reduce((m, o) => m.set(o.id, o), new Map),
result = array.filter(o => o === map.get(o.id));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
let cart = this.state.currentUser.cart;
let index = cart.indexOf(light);
if( index != -1) {
cart.splice( index,1);
}
or if you need to check the .use
let cart = this.state.currentUser.cart;
for( let i =0; i < cart.length; i++) {
if( cart[i].use === light.use) {
cart.splice(i,1);
break;
}
}
You could filter out all the items that have subsequent items match the relevant property, like so:
const newCartArray = cart.filter((light, i, array) => {
return !array.slice(i + 1).some(obj => obj.use === light.use);
})
This should work:
someFunction() {
let cart = this.state.currentUser.cart
const newCartArray = cart.filter((light, index) => {
return cart.slice(index + 1).findIndex(obj => {
obj.use === light.use
}) === -1;
})
cart = newCartArray
}

Sort entire nested object by property in JavaScript

I would like to loop through a deeply nested object, and sort each level based on a property. In this case its id
Here's my object (there will me more levels, I just added 3 levels here for readability):
const myObj = [
{
id: 15,
children: [
{
id: 9,
children: [
{
id: 4,
children: []
},
{
id: 1,
children: []
}
]
},
{
id: 4,
children: [
{
id: 35,
children: [
{
id: 12,
children: []
},
{
id: 8,
children: []
}
]
},
{
id: 30,
children: [],
}
]
},
]
},
{
id: 2,
children: [
{
id: 9,
children: []
},
{
id: 3,
children: []
},
]
}
]
Here's the desired output:
const myObj = [
{
id: 2,
children: [
{
id: 3,
children: []
},
{
id: 9,
children: []
}
]
},
{
id: 15,
children: [
{
id: 4,
children: [
{
id: 30,
children: [],
},
{
id: 35,
children: [
{
id: 8,
children: []
},
{
id: 12,
children: []
}
]
},
]
},
{
id: 9,
children: [
{
id: 1,
children: []
},
{
id: 4,
children: []
}
]
},
]
}
]
And here's my attempt at sorting it:
const myObj = [{id:15,children:[{id:9,children:[{id:4,children:[]},{id:1,children:[]}]},{id:4,children:[{id:35,children:[{id:12,children:[]},{id:8,children:[]}]},{id:30,children:[],}]},]},{id:2,children:[{id:9,children:[]},{id:3,children:[]},]}]
function sortByOrderIndex(obj) {
obj.sort((a, b) => (a.orderindex > b.orderindex) ? 1 : ((b.orderindex > a.orderindex) ? -1 : 0));
return obj;
}
function sortNestedObj(obj) {
sortByOrderIndex(obj);
for (let i = 0; i < obj.length; i++) {
const t = obj[i];
if (t.children.length !== 0) {
sortNestedObj(t.children);
} else {
return;
}
}
}
console.log(sortByOrderIndex(myObj))
I've created a function that sorts an object, and then tried to create another object that loops through each object that has children and sort those children using the first function. And if those children have children, then sort those and so forth until a child has no children.
You could recursively sort the array and it's object's children like this:
const myObj = [{id:15,children:[{id:9,children:[{id:4,children:[]},{id:1,children:[]}]},{id:4,children:[{id:35,children:[{id:12,children:[]},{id:8,children:[]}]},{id:30,children:[],}]},]},{id:2,children:[{id:9,children:[]},{id:3,children:[]},]}]
function sortArray(array) {
array.sort((a, b) => a.id - b.id);
array.forEach(a => {
if (a.children && a.children.length > 0)
sortArray(a.children)
})
return array;
}
console.log(sortArray(myObj))
You can make a recursive sorting function:
const myObj = [{id:15,children:[{id:9,children:[{id:4,children:[]},{id:1,children:[]}]},{id:4,children:[{id:35,children:[{id:12,children:[]},{id:8,children:[]}]},{id:30,children:[],}]},]},{id:2,children:[{id:9,children:[]},{id:3,children:[]},]}]
const orderChildren = obj => {
obj.children.sort((a, b) => a.id - b.id);
if (obj.children.some(o => o.children.length)) {
obj.children.forEach(child => orderChildren(child));
}
return obj;
};
const myNewObj = myObj.map(o => orderChildren(o)).sort((a, b) => a.id - b.id);
console.log(myNewObj);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can do:
const myObj = [{id: 15,children: [{id: 9,children: [{id: 4,children: []},{id: 1,children: []}]},{id: 4,children: [{id: 35,children: [{id: 12,children: []},{id: 8,children: []}]},{id: 30,children: [],}]},]},{id: 2,children: [{id: 9,children: []},{id: 3,children: []},]}];
const deepSortById = arr => (arr.forEach(a => a.children && deepSortById(a.children)), arr.sort((a, b) => a.id - b.id));
const result = deepSortById(myObj);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I created generic solution for sorting nested arrays by id. My solution works with any nested array and sorts it according to id property. Or by any other property you specify in the method's seconds parameter.
function sortNestedArrays(obj, sortPropertyName) {
Object.keys(obj).forEach((key) => {
if (Array.isArray(obj[key])) {
obj[key].sort((a, b) => a[sortPropertyName] - b[sortPropertyName]);
}
if (!!obj[key] && (typeof obj[key] === 'object' || Array.isArray(obj[key]))) {
sortNestedArrays(obj[key], sortPropertyName);
}
});
return obj;
}
Usage is following:
obj = sortNestedArrays(obj, 'id');

summarize values of objects with same attribute name

I have an array filled with objects. The following example shows the structure of the objects.
let array = [
{
data: [{name:'a', value:20}, {name:'b', value:10}, {name:'c', value:5}]
},
{
data: [{name:'d', value:20}, {name:'a', value:10}, {name:'e', value:40}]
},
{
data: [{name:'b', value:30}, {name:'a', value:5}]
}
];
I'm trying to iterate through all the data values and summarize all the identical letters and sum up there values in a new array. So the new array should look like this:
let array = [{name:'a', value:35}, {name:'b', value:40}, {name:'c', value:5}, {name:'d', value:20}, {name:'e', value:40}];
This is my current approach but I don't get it to work.
let prevData = '';
let summarizedArray = [];
for(let i = 0; i < array.length; i++) {
for(let j = 0; j < array[i].data.length; j++) {
if(prevData === array[i].data[j].name) {
let summarized = {
name: array[i].data[j].name;
value: prevData.value + array[i].data[j].value;
}
summarizedArray.push(summarized);
}
prevData = array[i].data[j];
}
}
// Edited Example:
let array = [
{
data: [{name:'a', value1:20, value2:90, value3:'foo'},
{name:'b', value1:30, value2:20, value3:'boo'}]
},
data: [{name:'c', value1:5, value2:10, value3:'goo'},
{name:'a', value1:30, value2:20, value3:'foo'}]
},
{
];
The values should be bundled by same names. The values of Value1 and Value2 should be added up and Value3 is always the same for each name.
So the result should look like this:
let result = [{name:'a', value1:50, value2:110, value3:'foo'},
{name:'b', value1:30, value2:20, value3:'boo'},
{name:'c', value1:5, value2:10, value3:'goo'}
];
You could take a Map and collect all values. Later get an array of object of the collected values.
let array = [{ data: [{ name: 'a', value: 20 }, { name: 'b', value: 10 }, { name: 'c', value: 5 }] }, { data: [{ name: 'd', value: 20 }, { name: 'a', value: 10 }, { name: 'd', value: 40 }] }, { data: [{ name: 'b', value: 30 }, { name: 'a', value: 5 }] }],
result = Array.from(
array.reduce(
(m, { data }) => data.reduce(
(n, { name, value }) => n.set(name, (n.get(name) || 0) + value),
m
),
new Map
),
([name, value]) => ({ name, value })
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
For a more convoluted object, you could take single properties to add, after a check for the type.
var array = [{ data: [{ name: 'a', value1: 20, value2: 90, value3: 'foo' }, { name: 'b', value1: 30, value2: 20, value3: 'boo' }] }, { data: [{ name: 'c', value1: 5, value2: 10, value3: 'goo' }, { name: 'a', value1: 30, value2: 20, value3: 'foo' }] }],
result = Array.from(
array.reduce(
(m, { data }) => {
data.forEach(o => {
var temp = m.get(o.name);
if (!temp) {
m.set(o.name, temp = {});
}
Object.entries(o).forEach(([k, v]) => {
if (k === 'name') return;
if (typeof v === 'number') {
temp[k] = (temp[k] || 0) + v;
} else {
temp[k] = v;
}
});
});
return m;
},
new Map
),
([name, value]) => Object.assign({ name }, value)
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories