I have the following array
var array = [
{
group: "FL",
list: [
{ key: "A", value: "Alaska" },
{ key: "B", value: "Brazil" },
{ key: "C", value: "California" }
]
},
{
group: "NZ",
list: [
{ key: "A", value: "Alaska" },
{ key: "B", value: "Brazil" },
{ key: "D", value: "Delhi" }
]
},
{
group: "QA",
list: [
{ key: "A", value: "Alaska" },
{ key: "B", value: "Brazil" },
{ key: "C", value: "California" }
]
}
]
I need to check the list array and if all the objects in the list array are exately same , then I need to merge it as below:
[
{
group: "FL,QA",
list: [
{ key: "A", value: "Alaska" },
{ key: "B", value: "Brazil" },
{ key: "C", value: "California" }
]
},
{
group: "NZ",
list: [
{ key: "A", value: "Alaska" },
{ key: "B", value: "Brazil" },
{ key: "D", value: "Delhi" }
]
}
]
I tried this by using reduce method to loop over the array and two other functions to compare the objects, but somehow its not working
array.reduce(async(acc, item) => {
const exist = await compareObjects(acc, item);
if (exist) {
acc[exist.index].group= exist.group ',' item.group;
} else {
acc.push(item)
}
return acc;
}, [])
async function compareObjects(o1, o2) {
for (let i = 0; i < o1.length; i ) {
const value = await checkObjs(o1[i].list, o2.list);
if(value) { return {index:i , group: o1[i].group} }
}
}
function checkObjs(arr1, arr2) {
return arr1.length === arr2.length && arr1.every((el, i) => objectsEqual(el, arr2[i]))
}
const objectsEqual = (o1, o2) =>
Object.keys(o1).length === Object.keys(o2).length
&& Object.keys(o1).every(p => o1[p] === o2[p]);
Any help would be appreciated . Thanks
CodePudding user response:
You can use Array.reduce()
to create a map of your input objects.
We'll create a function getListKey()
to create a unique key based on each object list.
Once we have our map, we can use Object.values()
to get the array result:
var array = [ { group: "FL", list: [ { key: "A", value: "Alaska" }, { key: "B", value: "Brazil" }, { key: "C", value: "California" } ] }, { group: "NZ", list: [ { key: "A", value: "Alaska" }, { key: "B", value: "Brazil" }, { key: "D", value: "Delhi" } ] }, { group: "QA", list: [ { key: "A", value: "Alaska" }, { key: "B", value: "Brazil" }, { key: "C", value: "California" } ] } ]
function getListKey(list) {
return list.sort(({key: a }, {key: b}) => a.localeCompare(b))
.map(({key, value}) => `${key}-${value}`).join(",");
}
const result = Object.values(array.reduce((acc, { group, list }) => {
const key = getListKey(list);
if (!acc[key]) {
acc[key] = { group, list };
} else {
acc[key].group = "," group;
}
return acc;
}, {}))
console.log('Result:', result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
CodePudding user response:
Your use of async
is what's tripping you up here, and I'm not sure your reason for using it.
To make your code work as is you need to await
the accumulator on each iteration, and assign the result of the reduce()
to something.
var array = [ { group: 'FL', list: [ { key: 'A', value: 'Alaska' }, { key: 'B', value: 'Brazil' }, { key: 'C', value: 'California' }, ], }, { group: 'NZ', list: [ { key: 'A', value: 'Alaska' }, { key: 'B', value: 'Brazil' }, { key: 'D', value: 'Delhi' }, ], }, { group: 'QA', list: [ { key: 'A', value: 'Alaska' }, { key: 'B', value: 'Brazil' }, { key: 'C', value: 'California' }, ], }, ];
function checkObjs(arr1, arr2) {
const objectsEqual = (o1, o2) =>
Object.keys(o1).length === Object.keys(o2).length && Object.keys(o1).every((p) => o1[p] === o2[p]);
return arr1.length === arr2.length && arr1.every((el, i) => objectsEqual(el, arr2[i]));
}
async function compareObjects(o1, o2) {
for (let i = 0; i < o1.length; i ) {
const value = await checkObjs(o1[i].list, o2.list);
if (value) {
return { index: i, group: o1[i].group };
}
}
}
// assign the result of reduce to a variable
const result = array.reduce(async (acc, item) => {
acc = await acc; // await the returned accumulator Promise
const exist = await compareObjects(acc, item);
if (exist) {
acc[exist.index].group = exist.group ',' item.group;
} else {
acc.push(item);
}
return acc;
}, []);
result.then((r) => console.log(r));
.as-console-wrapper { max-height: 100% !important; top: 0; }
CodePudding user response:
I think the way I would suggest going about this problem is by breaking it apart and (hopefully) using library functions to tackle some of the more complicated bits. For example with lodash
you could say
import isEqual from "lodash/isEqual";
const arr = [
{
group: "FL",
list: [
{ key: "A", value: "Alaska" },
{ key: "B", value: "Brazil" },
{ key: "C", value: "California" }
]
},
{
group: "NZ",
list: [
{ key: "A", value: "Alaska" },
{ key: "B", value: "Brazil" },
{ key: "D", value: "Delhi" }
]
},
{
group: "QA",
list: [
{ key: "A", value: "Alaska" },
{ key: "B", value: "Brazil" },
{ key: "C", value: "California" }
]
}
];
function groupBy<T, R>(
a: T[],
iteritem: (t: T) => R,
compare: (a: R, b: R) => boolean = isEqual
) {
const groups: T[][] = [];
const rs = a.map(iteritem);
for (let i = 0; i < rs.length; i ) {
let added = false;
const r = rs[i];
for (let j = 0; j < groups.length; j ) {
if (compare(r, iteritem(groups[j][0]))) {
groups[j].push(a[i]);
added = true;
break;
}
}
if (!added) {
groups.push([a[i]]);
}
}
return groups;
}
const grouped = groupBy(arr, (a) => a.list);
const combined = [];
for (const g of grouped) {
combined.push({
group: g.map(({ group }) => group).join(","),
list: g[0].list
});
}
console.log(JSON.stringify(combined, undefined, 2));
This isn't as much of a one off answer since groupBy
could be reused. I originally wanted to use groupBy
from lodash
but it doesn't accept a custom equality function.
CodePudding user response:
This is one possible solution:
const sorted = [];
for (let i = 0; i < groups.length; i ) {
const identicalLists = [];
for (let j = i; j < groups.length; j ) {
const isIdentical =
JSON.stringify(groups[i].list) === JSON.stringify(groups[j].list);
const found = !!sorted.flat().find((item) => item === groups[j].group);
if (isIdentical && !found) {
identicalLists.push(groups[j].group);
}
}
if (identicalLists.length > 0) {
sorted.push(identicalLists);
}
}
const answer = sorted.map((item) => {
const first = groups.find((group) => group.group === item[0]);
return { group: item, list: first.list };
});
CodePudding user response:
Reduce does not work with async/await
. If you don't have async
code - one that fetches something from an API or uses data from a Promise, you should remove the async/await
, because it is synchronous.
If the code you have uses some async API - try using something like:
export const reduceAsync = async (array, transformer, initialvalue) => {
let accumolator = typeof initialValue !== 'undefined' ? initialValue : array[0];
for (let i = 0; i < array.length; i ) {
accumolator = await transformer(accumolator, array[i], i, array);
}
return accumolator;
};
The function above is reusable and follows the spec defined here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce