Related
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
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; }
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 JSON.stringify(list.sort(({ key: a }, { key: b }) => a.localeCompare(b)));
}
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; }
Another way of approaching this is again using Array.reduce(), but using the lodash _.isEqual() function for list comparison. This performs a deep comparison. We'd use this along with Array.find() to get any list duplicate.
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" } ] } ]
const result = array.reduce((acc, cur) => {
const foundItem = acc.find(item => _.isEqual(item.list, cur.list));
if (foundItem) {
foundItem.group += `,${cur.group}`;
} else {
acc.push(cur);
}
return acc;
}, [])
console.log('Result:', result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" referrerpolicy="no-referrer"></script>
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.
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 };
});
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
I have a similar problem to this (Get a tree like structure out of path string). I tried to use the provided solution but can not get it to work in Angular.
The idea is to the separate incoming path strings (see below) and add them to an object and display them as a tree.
pathStrings: string[] = [
"PathA/PathA_0",
"PathA/PathA_1",
"PathA/PathA_2/a",
"PathA/PathA_2/b",
"PathA/PathA_2/c"
];
let tree: Node[] = [];
for (let i = 0; i < this.pathStrings.length; i++) {
tree = this.addToTree(tree, this.pathStrings[i].split("/"));
}
addToTree(root: Node[], names: string[]) {
let i: number = 0;
if (names.length > 0) {
for (i = 0; i < root.length; i++) {
if (root[i].name == names[0]) {
//already in tree
break;
}
}
if (i == root.length) {
let x: Node = { name: names[0] };
root.push(x);
}
root[i].children = this.addToTree(root[i].children, names.slice(1));
}
return root;
}
The result is supposed to look like this:
const TREE_DATA: Node[] = [
{
name: "PathA",
children: [
{ name: "PathA_0" },
{ name: "PathA_1" },
{
name: "PathA_2",
children: [{ name: "a" }, { name: "b" }, { name: "c" }]
}
]
},
{
name: "PathB",
children: [
{ name: "PathB_0" },
{ name: "PathB_1", children: [{ name: "a" }, { name: "b" }] },
{
name: "PathC_2"
}
]
},
{
name: "PathC",
children: [
{ name: "PathB_0" },
{ name: "PathB_1", children: [{ name: "a" }, { name: "b" }] },
{
name: "PathC_2"
}
]
}
];
Here is the Stackblitz Link (https://stackblitz.com/edit/angular-h3btn5?file=src/app/tree-flat-overview-example.ts) to my intents.. Im trying for days now without success.. Thank you so much!!
In plain Javascript, you could reduce the array by using a recursive function for thesearching and assign then new child to a given node.
const
getTree = (node, names) => {
const name = names.shift();
let child = (node.children ??= []).find(q => q.name === name);
if (!child) node.children.push(child = { name });
if (names.length) getTree(child, names);
return node;
},
pathStrings = ["PathA/PathA_0", "PathA/PathA_1", "PathA/PathA_2/a", "PathA/PathA_2/b", "PathA/PathA_2/c"],
tree = pathStrings
.reduce((target, path) => getTree(target, path.split('/')), { children: [] })
.children;
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I have an object that represents a tree:
const obj = {
"1": {
id: "1",
children: ["1-1", "1-2"]
},
"1-1": {
id: "1-1",
children: ["1-1-1", "1-1-2"]
},
"1-2": {
id: "1-2",
children: []
},
"1-1-1": {
id: "1-1-1",
children: []
},
"1-1-2": {
id: "1-1-2",
children: []
}
};
The result is a list similar to:
<ul>
<li>
1
<ul>
<li>
1.1
<ul>
<li>1.1.1</li>
<li>1.1.2</li>
</ul>
</li>
<li>
1.2
</li>
</ul>
</li>
</ul>
What I need is to transform the object above to an array where items go in the order they do in the list representation, i.e. ['1', '1-1', '1-1-1', '1-1-2', '1-2']. Ids can be any so I can't rely on them. It's the order of items in the children property that matters.
Update
The final result should be ['1', '1-1', '1-1-1', '1-1-2', '1-2'] i.e. the order they come in the list from the top to the bottom.
I use DFS to parse. It can sort any depth data. (You can try the obj2)
const obj = {
"1": {
id: "1",
children: ["1-1", "1-2"]
},
"1-1": {
id: "1-1",
children: ["1-1-1", "1-1-2"]
},
"1-2": {
id: "1-2",
children: []
},
"1-1-1": {
id: "1-1-1",
children: []
},
"1-1-2": {
id: "1-1-2",
children: []
}
};
const obj2 = {
"2": {
id: "2",
children: ["2-1", "2-2", "2-3"]
},
"2-1": {
id: "2-1",
children: ["2-1-1", "2-1-2"]
},
"2-2": {
id: "2-2",
children: []
},
"2-3": {
id: "2-3",
children: []
},
"2-1-1": {
id: "2-1-1",
children: ["2-1-1-1", "2-1-1-2"]
},
"2-1-2": {
id: "2-1-2",
children: ["2-1-2-1"]
},
"2-1-1-1": {
id: "2-1-1-1",
children: []
},
"2-1-1-2": {
id: "2-1-1-2",
children: []
},
"2-1-2-1": {
id: "2-1-2-1",
children: []
},
};
/* DFS */
function sort(id) {
if (!sorted.includes(id)) {
sorted.push(id);
obj[id].children.forEach(sub => {
sort(sub);
});
}
}
/* MAIN */
let sorted = [];
for (let [id, value] of Object.entries(obj)) {
sort(id);
}
console.log(sorted.flat());
const obj={1:{id:"1",children:["1-1","1-2"]},"1-1":{id:"1-1",children:["1-1-1","1-1-2"]},"1-2":{id:"1-2",children:[]},"1-1-1":{id:"1-1-1",children:[]},"1-1-2":{id:"1-1-2",children:[]}};
const output = Object.keys(obj)
// remove every non root
Object.entries(obj).forEach(el => el[1].children.forEach(child => {
let index = output.indexOf(child)
if (index !== -1) {
output.splice(index, 1)
}
}))
for (let i = 0; i < output.length; i++) {
// for each get it's children
let children = obj[output[i]].children
// push them just behind it
output.splice(i + 1, 0, ...children)
}
console.log(output)
You could try a recursive call with the base condition to ignore the traversed node
const obj = {
"1": {
id: "1",
children: ["1-1", "1-2"],
},
"1-1": {
id: "1-1",
children: ["1-1-1", "1-1-2"],
},
"1-2": {
id: "1-2",
children: [],
},
"1-1-1": {
id: "1-1-1",
children: [],
},
"1-1-2": {
id: "1-1-2",
children: [],
},
}
function traverse(obj) {
const res = []
const traversed = {}
function getChildren(id) {
if (traversed[id]) {
return
}
res.push(id)
traversed[id] = true
obj[id].children.forEach((childId) => getChildren(childId))
}
for (const id in obj) {
getChildren(id)
}
return res
}
console.log(traverse(obj))
Hope this is what you are expecting ?
let ans = []
function recursiveCallObj(key){
!ans.includes(key) ? ans.push(key) : ""
for(let i=0; i< obj[key].children.length; i++){
if(!ans.includes(obj[key].children[i])){
recursiveCallObj(obj[key].children[i])
}
else{
return
}
}
}
for(let [key, value] of Object.entries(obj)){
if(value.children.length > 0){
recursiveCallObj(key)
}
else{
!ans.includes(key) ? ans.push(key) : ""
}
}
console.log(ans)
Given an array in this format:
[
[{
name: "name",
value: "My-name"
},
{
name: "qty",
value: "1"
},
{
name: "url",
value: "test.com"
},
{
name: "comment",
value: "my-comment"
}
],
[{
name: "name",
value: "My-name2"
},
{
name: "qty",
value: "3"
},
{
name: "url",
value: "test2.com"
}
],
[{
name: "name",
value: "My-name3"
},
{
name: "qty",
value: "1"
},
{
name: "url",
value: "test3.com"
},
{
name: "comment",
value: "my-comment3"
}
]
]
I'm looking to switch that to:
[
[
{ name: "My-name" },
{ qty: "1" },
{ url: "test.com" },
{ comment: "my-comment", }
],[
{ name: "My-name2" },
{ qty: "3" },
{ url: "test2.com",
],[
{ name: "My-name3", },
{ qty: "1", },
{ url: "test3.com", },
{ comment: "my-comment3", }
]
]
In other words, swapping out the array keys but maintaining the object structure within each array element.
I've tried looping over each element and can swap the keys out using something like:
newArray[iCount][item.name] = item.value;
However I'm then struggling to preserve the object order. Note that the comment field may or may not appear in the object.
With Array.map() function:
var arr = [
[{name:"name",value:"My-name"},{name:"qty",value:"1"},{name:"url",value:"test.com"},{name:"comment",value:"my-comment"}],
[{name:"name",value:"My-name2"},{name:"qty",value:"3"},{name:"url",value:"test2.com"}],
[{name:"name",value:"My-name3"},{name:"qty",value:"1"},{name:"url",value:"test3.com"},{name:"comment",value:"my-comment3"}]
],
result = arr.map(function(a){
return a.map(function(obj){
var o = {};
o[obj.name] = obj.value
return o;
});
});
console.log(result);
Check my moreBetterOutput value. I think will be better.
If you still need a result like your example in the question then you can check output value.
const input = [
[
{
name:"name",
value:"My-name"
},
{
name:"qty",
value:"1"
},
{
name:"url",
value:"test.com"
},
{
name:"comment",
value:"my-comment"
}
],
[
{
name:"name",
value:"My-name2"
},
{
name:"qty",
value:"3"
},
{
name:"url",
value:"test2.com"
}
],
[
{
name:"name",
value:"My-name3"
},
{
name:"qty",
value:"1"
},
{
name:"url",
value:"test3.com"
},
{
name:"comment",
value:"my-comment3"
}
]
]
const output = input.map(arr => arr.map(obj => ({[obj.name]: obj.value})))
const moreBetterOutput = output.map(arr => arr.reduce((acc, item, index) => {
acc[Object.keys(item)[0]] = item[Object.keys(item)[0]];
return acc;
}, {}) )
//console.log(output);
console.log(moreBetterOutput);
Another map function:
const result = array.map( subarray =>
Object.assign(...subarray.map( ({name, value}) => ({ [name] : value }) ))
);
I have a json array after the form submission using formSerialize function like this
[
{"name":"client_management-testmonitoring","value":"0"},
{"name":"client_operations-testmonitoring","value":"0"},
{"name":"tpl_management-testmonitoring","value":"0"},
{"name":"tpl_operations-testmonitoring","value":"0"},
{"name":"channel_partner-testmonitoring","value":"0"},
{"name":"operator-testmonitoring","value":"0"},
{"name":"financier-testmonitoring","value":"0"},
{"name":"client_management-test_monitoring_2","value":"0"},
{"name":"client_operations-test_monitoring_2","value":"0"},
{"name":"tpl_management-test_monitoring_2","value":"0"},
{"name":"tpl_operations-test_monitoring_2","value":"0"},
{"name":"channel_partner-test_monitoring_2","value":"0"},
{"name":"operator-test_monitoring_2","value":"0"},
{"name":"financier-test_monitoring_2","value":"0"},
{"name":"client_management-test_monitoring_3","value":"0"},
{"name":"client_operations-test_monitoring_3","value":"0"},
{"name":"tpl_management-test_monitoring_3","value":"0"},
{"name":"tpl_operations-test_monitoring_3","value":"0"},
{"name":"channel_partner-test_monitoring_3","value":"0"},
{"name":"operator-test_monitoring_3","value":"0"},
{"name":"financier-test_monitoring_3","value":"0"}
]
and i need to convert this array like this :
[
{
"role": [
{
"role_name": "client_management",
"report_name": [
{
"test_monitoring_1": 1,
"test_monitoring_2": 1,
"test_monitoring_3": 0
}
]
}
]
},
{
"role": [
{
"role_name": "financier",
"report_name": [
{
"test_monitoring_1": 1,
"test_monitoring_2": 0,
"test_monitoring_3": 1
}
]
}
]
}
]
am trying this code to get the multidimesional array.
var formData = $('#' + reportType).serializeArray(),matrix_array =[];
for (var u = 0; u < formData.length; u++) {
for (var user in formData[u])
{
if (user == 'name') {
var matrix_name_n = formData[u][user],
matrix_name_a = matrix_name_n.split('-'),
role_name = matrix_name_a[0],
parameter_name = matrix_name_a[1];
var matrix_array_2 = [];
}
if (user == 'value') {
var matrix_param_value = formData[u][user], matrix_array_3 = [matrix_param_value];
}
}
var matrix_array2 = {};
var matrix_array2 = {};
matrix_array2["role"] = role_name;
matrix_array2[parameter_name] = matrix_param_value;
matrix_array_2.push(matrix_array2);
matrix_array.push(matrix_array_2);
var insert_matrix = {};
insert_matrix = JSON.stringify(formData);
}
but am not getting the expected result.Please help someone to sort out this issue
You could split the name by minus sign and use the first part as role_name and the second part as report_name property of the inner object.
var data = [{ name: "client_management-testmonitoring", value: 0 }, { name: "client_operations-testmonitoring", value: 0 }, { name: "tpl_management-testmonitoring", value: 0 }, { name: "tpl_operations-testmonitoring", value: 0 }, { name: "channel_partner-testmonitoring", value: 0 }, { name: "operator-testmonitoring", value: 0 }, { name: "financier-testmonitoring", value: 0 }, { name: "client_management-test_monitoring_2", value: 0 }, { name: "client_operations-test_monitoring_2", value: 0 }, { name: "tpl_management-test_monitoring_2", value: 0 }, { name: "tpl_operations-test_monitoring_2", value: 0 }, { name: "channel_partner-test_monitoring_2", value: 0 }, { name: "operator-test_monitoring_2", value: 0 }, { name: "financier-test_monitoring_2", value: 0 }, { name: "client_management-test_monitoring_3", value: 0 }, { name: "client_operations-test_monitoring_3", value: 0 }, { name: "tpl_management-test_monitoring_3", value: 0 }, { name: "tpl_operations-test_monitoring_3", value: 0 }, { name: "channel_partner-test_monitoring_3", value: 0 }, { name: "operator-test_monitoring_3", value: 0 }, { name: "financier-test_monitoring_3", value: 0 }],
result = data.reduce(function (hash) {
return function (r, a) {
var parts = a.name.split('-');
if (!hash[parts[0]]) {
hash[parts[0]] = {};
r[0].role.push({ role_name: parts[0], report_name: [hash[parts[0]]] });
}
if (!parts[1].match(/\d$/)) {
parts[1] = 'test_monitoring_1';
}
hash[parts[0]][parts[1]] = a.value;
return r;
}
}(Object.create(null)), [{ report_name: "monitoring", role: [] }]);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }