Push data to parent in a recursive function - javascript

I have the following object:
let model = {
id: 1,
name: "model 1",
children: [{
id: 2,
name: "sub model 1",
children: [{
id: 3,
name: "criteria 1",
isCriteria: true,
answer: {
mark: 4
}
},
{
id: 4,
name: "criteria 2",
isCriteria: true
}
]
},
{
id: 5,
name: "sub model 2",
children: [{
id: 6,
name: "criteria 3",
isCriteria: true,
answer: {
mark: 4
}
},
{
id: 7,
name: "criteria 4",
isCriteria: true,
answer: {
mark: 2
}
}
]
}
]
};
I want in result the following object:
{
name: "model 1",
answer: {
mark: 3.5,
completion: 75
},
children: [{
name: "sub model 1",
answer: {
mark: 4,
completion: 50
},
children: [{
name: "criteria 1",
isCriteria: true,
answer: {
mark: 4
}
},
{
name: "criteria 2",
isCriteria: true
}
]
},
{
name: "sub model 2",
answer: {
mark: 3,
completion: 100
},
children: [{
name: "criteria 3",
isCriteria: true,
answer: {
mark: 4
}
},
{
name: "criteria 4",
isCriteria: true,
answer: {
mark: 2
}
}
]
}
]
}
EXPLICATION:
I want to push to every parent the following answer object:
{
mark: the sum of marks of all children/total of children (exclude children with no answer),
completion: (the sum of children with answer/total children) * 100
}
!! NB: The depth of the object is unknown.
I tried the following function, But it adds the answer object only to the first parent before last depth
function loopThoughModelChildren(node, parent) {
if (node == null) {
return;
};
if (node.isCriteria) {
if (!parent.tempAnswer) {
parent.tempAnswer = [];
}
parent.tempAnswer.push({ child: node.id, answer: node.answer });
}
if (node.children)
node.children.forEach(child => loopThoughModelChildren(child, node));
}

You could take a recursive approach with a look to the children property.
Then generate new nodes and assign the answers.
function convert(node) {
if (!node.children) return node;
var marks = 0,
completions = 0,
count = 0,
children = node.children.map(node => {
node = convert(node);
if (node.answer) {
marks += node.answer.mark;
completions += node.answer.completion || 100;
count++;
}
return node;
});
return { ...node, answer: { mark: marks / count, completion: completions / node.children.length }, children };
}
var model = { id: 1, name: "model 1", children: [{ id: 2, name: "sub model 1", children: [{ id: 3, name: "criteria 1", isCriteria: true, answer: { mark: 4 } }, { id: 4, name: "criteria 2", isCriteria: true }] }, { id: 5, name: "sub model 2", children: [{ id: 6, name: "criteria 3", isCriteria: true, answer: { mark: 4 } }, { id: 7, name: "criteria 4", isCriteria: true, answer: { mark: 2 } }] }] },
result = convert(model);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

Consolidate object value containing array : Javascript

I am having array of objects that look like this:
const test = {
cat1: {
id: "c1",
name: "category1",
items: [
{
itemName: "item1",
points: 1,
used: true
},
{
itemName: "item2",
points: 3,
used: false
},
{
itemName: "item3",
points: 5,
used: true
}
]
},
cat2: {
id: "c2",
name: "category2",
items: [
{
itemName: "item4",
points: 7,
used: true
},
{
itemName: "item5",
points: 9,
used: false
}
]
},
cat3: {
id: "c3",
name: "category3",
items: [
{
itemName: "item6"
}
]
}
};
I want the output to be an object something like this
{ available: 12 , used: 13}
Need to go over the items array, and derive the available and used based on if used boolean under each object. If its true, add it with used else add it with available entry.Some object may not have points and used , those need to be ignored.
Can someone help with the approach
Code that I tried
const result = Object.values(test).reduce(
(acc, obj) => {
for (let i = 0; i < obj.items.length; i++) {
if (obj.items[i].used) {
return (acc.used = acc.used + obj.items[i].used);
} else acc.available = acc.available + obj.items[i].available;
}
},
{ available: 0, used: 0 }
);
You could flat the arrays and add with the help of an array.
const
test = { cat1: { id: "c1", name: "category1", items: [{ itemName: "item1", points: 1, used: true }, { itemName: "item2", points: 3, used: false }, { itemName: "item3", points: 5, used: true }] }, cat2: { id: "c2", name: "category2", items: [{ itemName: "item4", points: 7, used: true }, { itemName: "item5", points: 9, used: false }] }, cat3: { id: "c3", name: "category3", items: [{ itemName: "item6" }] } },
result = Object
.values(test)
.flatMap(({ items }) => items)
.reduce((r, { points, used }) => {
if (points) r[['available', 'used'][+used]] += points;
return r;
}, { available: 0 , used: 0 });
console.log(result);
2nd request.
const
test = { cat1: { id: "c1", name: "category1", items: [{ itemName: "item1", points: 1, used: true }, { itemName: "item2", points: 3, used: false }, { itemName: "item3", points: 5, used: true }] }, cat2: { id: "c2", name: "category2", items: [{ itemName: "item4", points: 7, used: true }, { itemName: "item5", points: 9, used: false }] }, cat3: { id: "c3", name: "category3", items: [{ itemName: "item6" }] } },
result = Object
.values(test)
.flatMap(({ items }) => items)
.reduce((r, { points = 0, used = false }) => {
r.used += used && points;
r.available += points;
return r;
}, { available: 0 , used: 0 });
console.log(result);
You're returning on the first iteration of the loop. You need to increment acc.used or acc.available in the loop, but only return acc at the end of the loop.
You need to change the parameter of the callback function to curr to match how you use it inside the function.
obj.items[i].used is not a number to add. You should just increment the counter. And there's no obj.items[i].available.
const test = {
cat1: {
id: "c1",
name: "category1",
items: [{
itemName: "item1",
points: 1,
used: true
},
{
itemName: "item2",
points: 3,
used: false
},
{
itemName: "item3",
points: 5,
used: true
}
]
},
cat2: {
id: "c2",
name: "category2",
items: [{
itemName: "item4",
points: 7,
used: true
},
{
itemName: "item5",
points: 9,
used: false
}
]
},
cat3: {
id: "c3",
name: "category3",
items: [{
itemName: "item6"
}]
}
};
const result = Object.values(test).reduce(
(curr, obj) => {
obj.items.forEach(item => {
if (item.used) {
curr.used++;
} else {
curr.available++;
}
});
return curr;
}, {
available: 0,
used: 0
});
console.log(result);
Do first a map() and .flat(), than the .reduce():
const input = { cat1: { id: "c1", name: "category1", items: [ { itemName: "item1", points: 1, used: true }, { itemName: "item2", points: 3, used: false }, { itemName: "item3", points: 5, used: true } ] }, cat2: { id: "c2", name: "category2", items: [ { itemName: "item4", points: 7, used: true }, { itemName: "item5", points: 9, used: false } ] }, cat3: { id: "c3", name: "category3", items: [ { itemName: "item6" } ] } };
const result = Object.values(input).map(obj => obj.items).flat().reduce((acc, obj) => {
if(obj.used) {
acc.used += obj.points;
} else {
acc.available += obj.points || 0;
}
return acc;
}, {available: 0, used: 0})
console.log(result);
Output:
{
"available": 12,
"used": 13
}
UPDATE 1 to get overall points for available:
const input = { cat1: { id: "c1", name: "category1", items: [ { itemName: "item1", points: 1, used: true }, { itemName: "item2", points: 3, used: false }, { itemName: "item3", points: 5, used: true } ] }, cat2: { id: "c2", name: "category2", items: [ { itemName: "item4", points: 7, used: true }, { itemName: "item5", points: 9, used: false } ] }, cat3: { id: "c3", name: "category3", items: [ { itemName: "item6" } ] } };
const result = Object.values(input).map(obj => obj.items).flat().reduce((acc, obj) => {
acc.available += obj.points || 0;
if(obj.used) {
acc.used += obj.points;
}
return acc;
}, {available: 0, used: 0})
console.log(result);
Output:
{
"available": 25,
"used": 13
}

How to flatten an object that has array of objects in Javascript

I am trying to solve this question it needs me to flatten this object parent that it has children each parent has 2 children, and each child has 2 children and so on....
My goal is to flatten this to one single object.
const par = {
id: 1,
name: "parent",
children: [{
id: 2,
name: "child 1",
children:[{
id: 4,
name: "child 3",
children: [],
},{
id: 5,
name: "child 4 ",
}]
},{
id: 3,
name: "child 2",
children: [{
id: 6,
name: "child 5",
},{
id: 7,
name: "child 6",
children: []
}]
}]
}
I tried function, but it returns an array from
Deep Flatten JavaScript Object Recursively
function flat(r, a) {
let b = {};
Object.keys(a).forEach(function (k) {
if (k !== 'children') {
b[k] = a[k];
}
});
r.push(b);
if (Array.isArray(a.children)) {
b.children = a.children.map(function (a) { return a.id;});
return a.children.reduce(flat, r);
}
return r;
}
You still owe us a description of your desired output. But if you want something as simple as this:
[
{id: 1, name: "parent"},
{id: 2, name: "child 1"},
{id: 4, name: "child 3"},
{id: 5, name: "child 4"},
{id: 3, name: "child 2"},
{id: 6, name: "child 5"},
{id: 7, name: "child 6"}
]
Then a depth-first recursive function can be as simple as this:
const flatten = ({children = [], ...rest}) => [rest, ...children .flatMap (flatten)]
const par = {id: 1, name: "parent", children: [{id: 2, name: "child 1", children: [{id: 4, name: "child 3", children: []}, {id: 5, name: "child 4 ", }]}, {id: 3, name: "child 2", children: [{id: 6, name: "child 5", }, {id: 7, name: "child 6", children: []}]}]}
console .log (flatten (par))
.as-console-wrapper {max-height: 100% !important; top: 0}
If you wanted to include a parentId field, using null for root-level objects, it's only slightly more complex:
const flatten = ({id, children = [], ...rest}, parentId = null) => [
{id, ...rest, parentId}, ...children .flatMap (c => flatten(c, id))
]
Here's an effective technique using a recursive generator flat -
function *flat({ children = [], ...t }, parentId = null) {
yield { ...t, parentId }
for (const child of children)
yield *flat(child, t.id)
}
const par = {id: 1,name: "parent",children: [{id: 2,name: "child 1",children:[{id: 4,name: "child 3",children: [],},{id: 5,name: "child 4 ",}]},{id: 3,name: "child 2",children: [{id: 6,name: "child 5",},{id: 7,name: "child 6",children: []}]}]}
console.log(Array.from(flat(par)))
.as-console-wrapper { min-height: 100%; top: 0; }
You can collect all the results of a generator using Array.from -
[
{
"id": 1,
"name": "parent",
"parentId": null
},
{
"id": 2,
"name": "child 1",
"parentId": 1
},
{
"id": 4,
"name": "child 3",
"parentId": 2
},
{
"id": 5,
"name": "child 4 ",
"parentId": 2
},
{
"id": 3,
"name": "child 2",
"parentId": 1
},
{
"id": 6,
"name": "child 5",
"parentId": 3
},
{
"id": 7,
"name": "child 6",
"parentId": 3
}
]
Or you can simply iterate thru the generator's result directly -
for (const flatNode of flat(par)) {
// do something with flatNode ...
}
See this related Q&A for a technique to convert the flat tree back to a recursive tree or graph.
You can try this
function flatTree(tree, parentId = null) {
const { id, name, children } = tree;
const result = [{ id, name, parentId }];
if (Array.isArray(children)) {
children.forEach((child) => {
result.push(...flatTree(child, id));
});
}
return result;
}
const par = {
id: 1,
name: "parent",
children: [
{
id: 2,
name: "child 1",
children: [
{
id: 4,
name: "child 3",
children: [],
},
{
id: 5,
name: "child 4 ",
},
],
},
{
id: 3,
name: "child 2",
children: [
{
id: 6,
name: "child 5",
},
{
id: 7,
name: "child 6",
children: [],
},
],
},
],
};
console.log(flatTree(par));
/**
* Output:
* [
{ id: 1, name: 'parent', parentId: null },
{ id: 2, name: 'child 1', parentId: 1 },
{ id: 4, name: 'child 3', parentId: 2 },
{ id: 5, name: 'child 4 ', parentId: 2 },
{ id: 3, name: 'child 2', parentId: 1 },
{ id: 6, name: 'child 5', parentId: 3 },
{ id: 7, name: 'child 6', parentId: 3 }
]
*/
Here is a solution using object-scan. Reinventing the wheel is typically not as bug-free, flexible or maintainable as using a battle-tested library!
.as-console-wrapper {max-height: 100% !important; top: 0}
<script type="module">
import objectScan from 'https://cdn.jsdelivr.net/npm/object-scan#18.4.0/lib/index.min.js';
const par = { id: 1, name: 'parent', children: [{ id: 2, name: 'child 1', children: [{ id: 4, name: 'child 3', children: [] }, { id: 5, name: 'child 4 ' }] }, { id: 3, name: 'child 2', children: [{ id: 6, name: 'child 5' }, { id: 7, name: 'child 6', children: [] }] }] };
const fn = objectScan(['**{children[*]}.id'], {
rtn: ({ parent: { id, name } }) => ({ id, name })
});
const r = fn(par);
console.log(r);
/* => [
{ id: 7, name: 'child 6' },
{ id: 6, name: 'child 5' },
{ id: 3, name: 'child 2' },
{ id: 5, name: 'child 4 ' },
{ id: 4, name: 'child 3' },
{ id: 2, name: 'child 1' },
{ id: 1, name: 'parent' }
] */
</script>
Disclaimer: I'm the author of object-scan

How do I output the child array correctly? [duplicate]

This question already has answers here:
Build tree array from flat array in javascript
(34 answers)
Closed 1 year ago.
How do I output the child array correctly?
const arr = [
[{ id: 0, parrentId: null, title: "Main tab", parrent: true }],
[{ id: 1, parrentId: 0, title: "Main child 1", parrent: false }],
[{ id: 2, parrentId: 0, title: "Main child 2", parrent: false }],
[{ id: 3, parrentId: 2, title: "Main tab 2", parrent: true }],
[{ id: 4, parrentId: 3, title: "Main child tab 2", parrent: false }]
];
How to output arrays according to child > parrent? But so that the id is parrentId === id
Main tab:
1) Main child 1
2) Main child 2
1) Main tab 2
1)Main child tab 2
I'm trying to
if (arr) {
const child = arr.filter(({ hasChild }) => hasChild);
const notChild = arr.filter(({ hasChild }) => !hasChild);
const test = { ...child, notChild };
console.log(test);
}
If I understood correctly, maybe it is something like this:
const arr = [
[{ id: 0, parentId: null, title: "Main tab", parent: true }],
[{ id: 1, parentId: 0, title: "Main child 1", parent: false }],
[{ id: 2, parentId: 0, title: "Main child 2", parent: false }],
[{ id: 3, parentId: 2, title: "Main tab 2", parent: true }],
[{ id: 4, parentId: 3, title: "Main child tab 2", parent: false }]
];
arr.reduce((acc, data) => {
if(data[0].parent) {
acc = acc.concat(data[0]);
} else {
acc = acc.map(ac => {
if(ac.id === data[0].parentId) {
return {
...ac,
child: ac.child ? ac.child.concat(data[0]) : [].concat(data[0])
};
} else {
return ac;
}
});
}
return acc;
}, []);

Filter object from array based on multiple nested values

Basically I'm trying to figure out the cleanest way to select one item from an array, only if all certain values exist.
const filterValues = ['blue', '30cm', 'true'];
const products = [
{
details: [
{ id: 1, value: 'red' },
{ id: 2, value: '30cm' },
{ id: 3, value: 'true' },
{ id: 4, value: '123432'}
],
name: "Product 1"
},
{
details: [
{ id: 5, value: 'blue' },
{ id: 6, value: '30cm' },
{ id: 7, value: 'true' },
{ id: 8, value: '98348'}
],
name: "Product 2"
},
{
details: [
{ id: 9, value: 'black' },
{ id: 10, value: '40cm' },
{ id: 11, value: 'false' },
{ id: 12, value: '578347'}
],
name: "Product 3"
},
]
Only Product 2 contains all the filter values, so I want to return that product.
I have tried:
products.filter(p => {
p.details.find(k => filterValues.includes(k.value));
})
but this returns if any of the values satisfies the condition rather than if all of them are included. This is the main issue here. I'm struggling with finding a way to filter if only all these values int he array are present in the object.
Use Array#every.
const filterValues = ['blue', '30cm', 'true'];
const products = [
{
details: [
{ id: 1, value: 'red' },
{ id: 2, value: '30cm' },
{ id: 3, value: 'true' },
{ id: 4, value: '123432'}
],
name: "Product 1"
},
{
details: [
{ id: 5, value: 'blue' },
{ id: 6, value: '30cm' },
{ id: 7, value: 'true' },
{ id: 8, value: '98348'}
],
name: "Product 2"
},
{
details: [
{ id: 9, value: 'black' },
{ id: 10, value: '40cm' },
{ id: 11, value: 'false' },
{ id: 12, value: '578347'}
],
name: "Product 3"
},
]
console.log(products.filter(p => filterValues.every(fv => p.details.map(d => d.value).includes(fv))));

typescript- filter array of objects based on another array

I have an array of objects like given below
readonly allItems = [
{
id: 0,
title: "Item 0",
belongsTo: 'admin'
},
{
id: 1,
title: "Item 1",
belongsTo: 'user'
},
{
id: 2,
title: "Item 2",
belongsTo: 'all'
},
{
id: 3,
title: "Item 3",
belongsTo: 'user'
},
{
id: 4,
title: "Item 4",
belongsTo: 'all'
}
];
And I have an array of numbers like given below
let selItems = [0,2,4];
What I'm trying to do is, filter the allItems array based on selItems array
For doing that, I wrote the following code, which is obviously wrong.
for(let i=0; i< this.allItems.length; i++){
if(selItems.includes(this.allItems[i].id)){
tempMenu.push(this.allItems[i]);
}
console.log(tempMenu);
}
I'm getting the following as output
[{
id: 0,
title: "Item 0",
belongsTo: 'admin'
}]
The result I'm expecting is like this:
[
{
id: 0,
title: "Item 0",
belongsTo: 'admin'
},
{
id: 2,
title: "Item 2",
belongsTo: 'all'
},
{
id: 4,
title: "Item 4",
belongsTo: 'all'
}
]
Can anyone show me the right way to do this?
Thanks!
You might use .map instead:
const allItems = [{
id: 0,
title: "Item 0",
belongsTo: 'admin'
},
{
id: 1,
title: "Item 1",
belongsTo: 'user'
},
{
id: 2,
title: "Item 2",
belongsTo: 'all'
},
{
id: 3,
title: "Item 3",
belongsTo: 'user'
},
{
id: 4,
title: "Item 4",
belongsTo: 'all'
}
];
const selItems = [0, 2, 4];
const output = selItems.map(num => allItems.find(({ id }) => id === num));
console.log(output);
To reduce computational complexity to O(N) instead of O(N^2), you can transform it into an object indexed by id first:
const allItems = [{
id: 0,
title: "Item 0",
belongsTo: 'admin'
},
{
id: 1,
title: "Item 1",
belongsTo: 'user'
},
{
id: 2,
title: "Item 2",
belongsTo: 'all'
},
{
id: 3,
title: "Item 3",
belongsTo: 'user'
},
{
id: 4,
title: "Item 4",
belongsTo: 'all'
}
];
const selItems = [0, 2, 4];
const allItemsById = allItems.reduce((a, item) => {
a[item.id] = item;
return a;
}, {});
const output = selItems.map(num => allItemsById[num]);
console.log(output);
Or with filter:
const allItems = [{
id: 0,
title: "Item 0",
belongsTo: 'admin'
},
{
id: 1,
title: "Item 1",
belongsTo: 'user'
},
{
id: 2,
title: "Item 2",
belongsTo: 'all'
},
{
id: 3,
title: "Item 3",
belongsTo: 'user'
},
{
id: 4,
title: "Item 4",
belongsTo: 'all'
}
];
const selItemsSet = new Set([0, 2, 4]);
const output = allItems.filter(({ id }) => selItemsSet.has(id));
console.log(output);

Categories