Find maximum id value in a deeply nested array of objects - javascript

I have an tree data structure with each object containing children:
const data = {
id: 1,
name: "John",
parent_id: null,
children: [{
id: 2,
name: "Tess",
parent_id: 1,
children: []
},
{
id: 3,
name: "Tom",
parent_id: 1,
children: [{
id: 4,
name: "Harry",
parent_id: 3,
children: [{
id: 7,
name: "Thabo",
parent_id: 4,
children: []
}]
},
{
id: 5,
name: "Mary",
parent_id: 3,
children: []
},
{
id: 6,
name: "Madge",
parent_id: 3,
children: []
}
]
}
]
}
Before I can add a new object to the tree, I need to determine the highest id value currently used, so I can assign the next available number as id for the new user.
To do this I created a new variable with an initial value of 0. Then I iterate over each object in the tree, and if the object's id is higher than the new id, I assign the new id the current id's value (the idea being taking the final value and adding 1 to get the new id).
let newUserID = 0;
const newID = ( root, idKey ) => {
if ( root.id > idKey ) {
idKey = root.id;
}
root.children.forEach( ( obj ) => {
newID( obj, idKey );
});
return idKey;
}
newUserID = newID( data, newUserID );
console.log( newUserID );
I expected this to return the highest id in the tree as the final value, but what actually happens is that, while the new id does increase until it matches the maximum value, it then starts decreasing again, ending on 1.
This can be seen in this JSFiddle which includes some logging to show the value of the new ID at different points in the function.
I've since solved the issue using a different approach (extracting the id values to a new array, and using Math.max() to find the highest value), but I'd like to understand why my initial approach didn't work as expected. I can see the idKey value is being updated, but then the previous value gets passed back on the recursive call, but I don't know why that's happening or how to prevent it.

First, as to why your code is broken: You just missed an assignment. Where you have
newID( obj, idKey );
you are ignoring the resulting value. You need to assign it back to idKey:
idKey = newID( obj, idKey );
That will solve your problem. We should also note that the variable name newUserID is a bit of a misnomer, since it's not the the new one you will use but the highest one found. Perhaps highestUserID would be less confusing?
However, we should point out that this can be written much more simply, using Math .max to do the heavy lifting and a dollop of recursion. Here's how I might write this:
const maxId = ({id, children = []}) =>
Math .max (id, ... children .map (maxId))
const data = {id: 1, name: "John", parent_id: null, children: [{id: 2, name: "Tess", parent_id: 1, children: []}, {id: 3, name: "Tom", parent_id: 1, children: [{id: 4, name: "Harry", parent_id: 3, children: [{id: 7, name: "Thabo", parent_id: 4, children: []}]}, {id: 5, name: "Mary", parent_id: 3, children: []}, {id: 6, name: "Madge", parent_id: 3, children: []}]}]}
console .log (maxId (data))

Simply assign the returned value of the recursive call to idKey :
let newUserID = 0;
const newID = ( root, idKey ) => {
if ( root.id > idKey ) {
idKey = root.id;
}
root.children.forEach( ( obj ) => {
idKey = newID( obj, idKey ); // <--------
});
return idKey;
}
newUserID = newID( data, newUserID );
console.log( newUserID );
Without this assignment, no matter how much you recurse, the value returned will depend only on the result of the if statement at the top. This explains the logs you were getting.

You can use recursion to solve this. Like below
const data = {
id: 1,
name: "John",
parent_id: null,
children: [
{
id: 2,
name: "Tess",
parent_id: 1,
children: [],
},
{
id: 3,
name: "Tom",
parent_id: 1,
children: [
{
id: 4,
name: "Harry",
parent_id: 3,
children: [
{
id: 7,
name: "Thabo",
parent_id: 4,
children: [],
},
],
},
{
id: 5,
name: "Mary",
parent_id: 3,
children: [],
},
{
id: 6,
name: "Madge",
parent_id: 3,
children: [],
},
],
},
],
};
const findMax = (value) => {
let max = -Infinity;
const _findMax = (data) => {
if (max < data.id) max = data.id;
data.children.forEach(_findMax);
};
_findMax(value);
return max;
};
console.log(findMax(data));

You can do:
const data = {id: 1,name: 'John',parent_id: null,children: [{ id: 2, name: 'Tess', parent_id: 1, children: [] },{id: 3,name: 'Tom',parent_id: 1,children: [{id: 4,name: 'Harry',parent_id: 3,children: [{ id: 7, name: 'Thabo', parent_id: 4, children: [] }],},{ id: 5, name: 'Mary', parent_id: 3, children: [] },{ id: 6, name: 'Madge', parent_id: 3, children: [] },],},],}
const arr = [...JSON.stringify(data).matchAll(/"id":(\d+)/g)].map(([, n]) => +n)
const result = Math.max(...arr)
console.log(result)

Related

How to access last object inside nested array of objects and update its property without passing id or any parameters

I want to access last object inside nested array of objects and change its property value, I don't know its id or any any other property value
const arr = [ {id: 1, comment:'parent 01', parentId:null, reply:true, children:[{id: 11, comment:'child', reply:true, parentId:1, children:[{id: 21, comment:'super child ', reply:true,parentId:11 }] }] }, {id: 2, comment:'parent 02', reply:true, parentId:null } ]
I want to access this below object and change its property value:
{id: 21, comment:'super child ', reply:true, parentId:11 }
// result should be:
{id: 21, comment:'super child ', reply:false, parentId:11 }
Need to do it recursively and when the depth is greater than 0 and it doesn't have any children then need to modify the object(s).
const arr = [{
id: 1,
comment: 'parent 01',
parentId: null,
reply: true,
children: [{
id: 11,
comment: 'child',
reply: true,
parentId: 1,
children: [{
id: 21,
comment: 'super child ',
reply: true,
parentId: 11
}]
}]
}, {
id: 2,
comment: 'parent 02',
reply: true,
parentId: null
}];
const build = (arr, depth) => {
const nodes = [];
arr.forEach((val) => {
if (depth > 0 && !val["children"]) { //Base case
nodes.push({ ...val,
reply: false
});
return;
}
nodes.push(val["children"] ? { ...val,
"children": build(val["children"], depth + 1)
} : val);
});
return nodes;
}
console.log(build(arr, 0));

How to make recursive function vue.js to make data from children

I am using vue.js and v-tree + vue2vis network,
I have a tree with all my items like this one :
items: [
{
id: 1,
name: 'root',
children: [
{
id: 2,
name: 'child1',
children: [
{
id: 3,
name: 'child3',
},
{
id: 4,
name: 'child34',
},
],
},
{
id: 5,
name: 'subroot',
children: [
{
id: 6,
name: 'Mike',
children:[
{
id: 7,
name: 'Mini Mike',
}
]
},
{
id: 8,
name: 'Hunt',
},
],
},
{
id: 9,
name: 'test',
children: [
{
id: 10,
name: 'Brandon',
},
{
id: 11,
name: 'Sean',
},
],
},
],
},
],
And what i want to do is when i click on a item in the tree it will generate data for the network like this:
nodes: [{'id':1 , label: 'root'},{'id':2 , label: 'child1'},{'id':3 , label: 'child3'}]
and so on for all the children and parents
same goes for the edage i want to create conection between parent and child
edage: [{'from': 1, 'to':2},{'from': 2, 'to':3}]
I try this function for this idea but its not working
makeNetConnection(items , itemKey) {
//items -> all the tree data
//itemKey-> its the item i click on the tree that i want to create the view
for (let item of items) {
if (item.name == itemKey) {
this.nodes.push({'id':item.id , 'label':item.name});
return item;
}
if (item.children) {
let i = this.makeNetConnection(item.children ,itemKey)
if (i) {
this.nodes.push({'id':item.id , 'label':item.name});
this.edges.push({'from': item.id , 'to': i.id});
return i;
}
}
}
its duplicate the data in the arrays and not make connect with the parent
i expect to have [{'from': 1, 'to':2},{'from': 2, 'to':3},{'from': 2, 'to':4}]
and os on for all the items
but i have [{'from': 1, 'to':2},{'from': 1, 'to':3} , {'from': 1, 'to':2},{'from': 1, 'to':3}, {'from': 1, 'to':4}, {'from': 1, 'to':4}]
i dont get the middel connection
any idea how to make its work?
Maybe overkill, but use traverse
const traverse = require('traverse');
const returnData=[];
traverse.forEach(function(item){
returnData.push({id:item.id,label:item.value});
});
I would build this upon simpler functions to collect the nodes and to collect the edges. It means running two traversals of your tree, but it makes for much simpler code.
Here we have a recursive function to flatten the nodes of a tree into an array, transforming the name property to a label one as we go. And we have a second recursive function to collect the edges as from-to pairs.
Then we write a very simple function to combine them:
const collectNodes = (xs) =>
xs .flatMap (({id, name, children = []}) => [
{id, label: name},
... collectNodes (children)
])
const collectEdges = (xs) =>
xs .flatMap (({id: from, children = []}) => [
... children .map (({id: to}) => ({from, to})),
... collectEdges (children),
])
const collect = (items) => ({
nodes: collectNodes (items),
edges: collectEdges (items)
})
const items = [{id: 1, name: "root", children: [{id: 2, name: "child1", children: [{id: 3, name: "child3"}, {id: 4, name: "child34"}]}, {id: 5, name: "subroot", children: [{id: 6, name: "Mike", children: [{id: 7, name: "Mini Mike"}]}, {id: 8, name: "Hunt"}]}, {id: 9, name: "test", children: [{id: 10, name: "Brandon"}, {id: 11, name: "Sean"}]}]}]
console .log (collect (items))
.as-console-wrapper {max-height: 100% !important; top: 0}
Could we do this in a single traversal? Certainly, but I think it would make for more convoluted code. I would only bother doing so if the performance of this is not acceptable.
I ignored this: "when i click on a item in the tree". I'm assuming that you want to convert all the data. If you only want it up to a certain node, then please add a clarification to the question.

Create nested array in Javascript

I'm trying to convert my data from API to my needs. Would like to create a nested array from plain array. I would like to group elements by parentId property, if parentId would not exist I would put it as a root. id value is unique. Like so (raw data):
[
{id: 1, name: 'sensor'},
{id: 2, name: 'sensor', parent: 1},
{id: 3, name: 'sensor', parent: 1},
{id: 4, name: 'sensor', parent: 3},
{id: 5, name: 'sensor'},
{id: 6, name: 'sensor', parent: 5}
]
Converted Data:
const results = [
{
id: 1,
name: "sensor",
children: [
{ id: 2, name: "sensor", parent: 1 },
{
id: 3,
name: "sensor",
parent: 1,
children: [{ id: 4, name: "sensor", parent: 3 }]
}
]
},
{ id: 5, name: "sensor", children: [{ id: 6, name: "sensor", parent: 5 }] }
];
I found this recursive method but it assumes that the parent property exist for every element in an array. In my example root level element would not have parent property.
function getNestedChildren(arr, parent) {
var out = []
for(var i in arr) {
if(arr[i].parent == parent) {
var children = getNestedChildren(arr, arr[i].id)
if(children.length) {
arr[i].children = children
}
out.push(arr[i])
}
}
return out
}
You could take an approach which uses both relations, one from children to parent and vice versa. At the end take the children of the root node.
This approach works for unsorted data.
var data = [{ id: 1, name: 'sensor' }, { id: 2, name: 'sensor', parent: 1 }, { id: 3, name: 'sensor', parent: 1 }, { id: 4, name: 'sensor', parent: 3 }, { id: 5, name: 'sensor' }, { id: 6, name: 'sensor', parent: 5 }],
tree = function (data, root) {
var t = {};
data.forEach(o => {
Object.assign(t[o.id] = t[o.id] || {}, o);
t[o.parent] = t[o.parent] || {};
t[o.parent].children = t[o.parent].children || [];
t[o.parent].children.push(t[o.id]);
});
return t[root].children;
}(data, undefined);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Given the limited amount of information (will update if more info is added).
The algorithm would be, given an array of data entries, check if entry has a parent and if that parent exists, in which case we want to add the entry to the array of children of the parent entry otherwise add the entry as a parent.
var dataFromAPI = [
{id: 1, name: 'sensor'},
{id: 2, name: 'sensor', parent: 1},
{id: 3, name: 'sensor', parent: 1},
{id: 4, name: 'sensor', parent: 3},
{id: 5, name: 'sensor'},
{id: 6, name: 'sensor', parent: 5}
];
var transformedData = { };
dataFromAPI.forEach(function(entry){
if(entry.parent !== undefined && entry.parent in transformedData) {
transformedData[entry.parent].children.push(entry);
} else {
entry["children"] = [];
transformedData[entry.id] = entry;
}
});
console.log(transformedData);
Please note:
there are a couple assumptions made within this algorithm/code. It assumes that all parent entries exist before their child entry. It also only accounts for two levels (parent or child), meaning a child cannot act as the parent (otherwise you'd have to store the children as an object and not an array)
use a for loop to go through each item.
check if parent property exists (or has value).
If not its a child item. Attach it to appropriate parent.
to check if property exists:
var myProp = 'prop';
if (myObj.hasOwnProperty(myProp)) {
alert("yes, i have that property");
}
try
let h={}, r=[]; // result in r
d.forEach(x=> (h[x.id]=x, x.children=[]) );
d.forEach(x=> x.parent ? h[x.parent].children.push(x) : r.push(x) );
let d = [
{id: 1, name: 'sensor'},
{id: 2, name: 'sensor', parent: 1},
{id: 3, name: 'sensor', parent: 1},
{id: 4, name: 'sensor', parent: 3},
{id: 5, name: 'sensor'},
{id: 6, name: 'sensor', parent: 5}
];
let h = {},r = []; // result in r
d.forEach(x => (h[x.id] = x, x.children = []));
d.forEach(x => x.parent ? h[x.parent].children.push(x) : r.push(x));
console.log(r);
If you want that parent element should not have a parent , than you can manually check and remove fields of an object in an array that has null parent. than you can make a tree... here is an example...
const arr2 = [
{id: 1, name: 'gender', parent: null, parent_id: null },
{id: 2, name: 'material', parent: null, parent_id: null },
{id: 3, name: 'male', parent: 1, parent_name: "gender" },
{ id: 5, name: 'female', parent: 1, parent_name: "gender" },
{ id: 4, name: 'shoe', parent: 3, parent_id: "male"},
]
let newarr=[];
for(let i=0 ; i< arr2.length; i++ ){
if(arr2[i].id){
if(newarr[i] != {} ){
newarr[i] = {}
}
newarr[i].id = arr2[i].id
}
if( arr2[i].name ){
newarr[i].name = arr2[i].name
}
if( arr2[i].parent ){
newarr[i].parent = arr2[i].parent
}
if( arr2[i].parent_id ){
newarr[i].parent_id = arr2[i].parent_id
}
}
console.log('newarr', newarr );
let tree = function (data, root) {
var obj = {};
data.forEach(i => {
Object.assign(obj[i.id] = obj[i.id] || {}, i);
obj[i.parent] = obj[i.parent] || {};
obj[i.parent].children = obj[i.parent].children || [];
obj[i.parent].children.push(obj[i.id]);
});
return obj[root].children;
}(newarr, undefined);
console.log('tree ', tree);

Remove matched object from deeply nested array of objects

I have a data tree structure with children:
{ id: 1,
name: "Dog",
parent_id: null,
children: [
{
id: 2,
name: "Food",
parent_id: 1,
children: []
},
{
id: 3,
name: "Water",
parent_id: 1,
children: [
{
id: 4,
name: "Bowl",
parent_id: 3,
children: []
},
{
id: 5,
name: "Oxygen",
parent_id: 3,
children: []
},
{
id: 6,
name: "Hydrogen",
parent_id: 3,
children: []
}
]
}
]
}
This represents a DOM structure that a user could select an item from to delete by clicking the corresponding button in the DOM.
I have a known text title of the selected item for deletion from the DOM set as the variable clickedTitle. I am having trouble finding an algorithm that will allow me to delete the correct object data from the deeply nested tree.
Here is my code:
function askUserForDeleteConfirmation(e) {
const okToDelete = confirm( 'Are you sure you want to delete the item and all of its sub items?' );
if(!okToDelete) {
return;
}
const tree = getTree(); // returns the above data structure
const clickedTitle = getClickedTitle(e); // returns string title of clicked on item from DOM - for example "Dog" or "Bowl"
const updatedTree = removeFromTree(tree, tree, clickedTitle);
return updatedTree;
}
function removeFromTree(curNode, newTree, clickedTitle) {
if(curNode.name === clickedTitle) {
// this correctly finds the matched data item to delete but the next lines don't properly delete it... what to do?
const index = curNode.children.findIndex(child => child.name === clickedTitle);
newTree = curNode.children.slice(index, index + 1);
// TODO - what to do here?
}
for(const node of curNode.children) {
removeFromTree(node, newTree, clickedTitle);
}
return newTree;
}
I have tried to use the info from Removing matched object from array of objects using javascript without success.
If you don't mind modifying the parameter tree in-place, this should do the job. Note that it'll return null if you attempt to remove the root.
const tree = { id: 1, name: "Dog", parent_id: null, children: [ { id: 2, name: "Food", parent_id: 1, children: [] }, { id: 3, name: "Water", parent_id: 1, children: [ { id: 4, name: "Bowl", parent_id: 3, children: [] }, { id: 5, name: "Oxygen", parent_id: 3, children: [] }, { id: 6, name: "Hydrogen", parent_id: 3, children: [] } ] } ] };
const removeFromTree = (root, nameToDelete, parent, idx) => {
if (root.name === nameToDelete) {
if (parent) {
parent.children.splice(idx, 1);
}
else return null;
}
for (const [i, e] of root.children.entries()) {
removeFromTree(e, nameToDelete, root, i);
}
return tree;
};
console.log(removeFromTree(tree, "Oxygen"));
Your current code is very much on the right track. However:
newTree = curNode.children.slice(index, index + 1);
highlights a few issues: we need to manipulate the parent's children array to remove curNode instead of curNode's own children array. I pass parent objects and the child index recursively through the calls, saving the trouble of the linear operation findIndex.
Additionally, slicing from index to index + 1 only extracts one element and doesn't modify curNode.children. It's not obvious how to go about using newArray or returning it through the call stack. splice seems like a more appropriate tool for the task at hand: extracting one element in-place.
Note that this function will delete multiple entries matching nameToDelete.
I like #VictorNascimento's answer, but by applying map then filter, each children list would be iterated twice. Here is an alternative with reduce to avoid that:
function removeFromTree(node, name) {
return node.name == name
? undefined
: {
...node,
children: node.children.reduce(
(children, child) => children.concat(removeFromTree (child, name) || []), [])
}
}
In the case you want a way to remove the items in-place, as #ggorlen proposed, I'd recommend the following solution, that is simpler in my opinion:
function removeFromTree(node, name) {
if (node.name == name) {
node = undefined
} else {
node.children.forEach((child, id) => {
if (!removeFromTree(child, name)) node.children.splice(id, 1)
})
}
return node
}
I've built the algorithm as follows:
function omitNodeWithName(tree, name) {
if (tree.name === name) return undefined;
const children = tree.children.map(child => omitNodeWithName(child, name))
.filter(node => !!node);
return {
...tree,
children
}
}
You can use it to return a new tree without the item:
noHydrogen = omitNodeWithName(tree, "Hydrogen")
If it's ok to use Lodash+Deepdash, then:
let cleaned = _.filterDeep([tree],(item)=>item.name!='Hydrogen',{tree:true});
Here is a Codepen
We use object-scan for many data processing tasks. It's powerful once you wrap your head around it. Here is how you could answer your question
// const objectScan = require('object-scan');
const prune = (name, input) => objectScan(['**[*]'], {
rtn: 'bool',
abort: true,
filterFn: ({ value, parent, property }) => {
if (value.name === name) {
parent.splice(property, 1);
return true;
}
return false;
}
})(input);
const obj = { id: 1, name: 'Dog', parent_id: null, children: [{ id: 2, name: 'Food', parent_id: 1, children: [] }, { id: 3, name: 'Water', parent_id: 1, children: [{ id: 4, name: 'Bowl', parent_id: 3, children: [] }, { id: 5, name: 'Oxygen', parent_id: 3, children: [] }, { id: 6, name: 'Hydrogen', parent_id: 3, children: [] }] }] };
console.log(prune('Oxygen', obj)); // return true iff pruned
// => true
console.log(obj);
// => { id: 1, name: 'Dog', parent_id: null, children: [ { id: 2, name: 'Food', parent_id: 1, children: [] }, { id: 3, name: 'Water', parent_id: 1, children: [ { id: 4, name: 'Bowl', parent_id: 3, children: [] }, { id: 6, name: 'Hydrogen', parent_id: 3, children: [] } ] } ] }
.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

Create Javascript Object Having Nested Parent Objects

I am working on a Nodejs project. I have to create a function which takes an object (a child category) like:
{
id: 65,
name: 'Outdoor',
parent_id: 2
}
Now I want my function to check for the parent category by using parent_id from database and return an array/object like this:
{
id: 2,
name: 'Furniture',
parent: {
id: 1,
name: 'Residential',
parent: {
id: ...,
name: ...,
parent: {
and so on..
}
}
}
}
This is what I have done so far:
* _get_category_parents(category, _array) {
if(_array === undefined) _array = []
if( category.parent_id !== 0 ) {
const c_parent = yield this.Database.from('categories').where('id', '=', category.parent_id)
_array.push({id: c_parent[0].id, name: c_parent[0].name})
yield this._get_category_parents(c_parent[0], _array)
}
return _array
}
And calling this function like this:
const parents = yield this._get_category_parents(category)
This returns me an array of parents like this:
[
{
"id": 2,
"name": "Furniture"
},
{
"id": 1,
"name": "Residential"
}
]
I want Residential object to be appended in Furniture's parent node.
I have spent too much time on this but not getting what I want. Any help would be deeply appreciated.
What you want to think about is a recursive solution.
Since you're calling a database, it's probably unlikely, but if the lookup by id is synchronous, you might do it with code something like the following (note that I'm faking a db here):
const getHierarchy = (lookup, child) => {
const {id, name, parent_id} = lookup(child) ||
{id: null, name: null, parent_id: 0}
return parent_id == 0
? {id, name, parent_id}
: {...{id, name}, ...{parent: getHierarchy(lookup, {parent_id})}}
}
const items = [
{id: 1, name: 'Residential', parent_id: 5},
{id: 2, name: 'Furniture', parent_id: 1},
{id: 3, name: 'Other', parent_id: 0},
{id: 4, name: 'FooBar', parent_id: 3},
{id: 5, name: 'Stuff', parent_id: 0}
]
const lookup = child => items.find(item => item.id == child.parent_id)
const item = {id: 65, name: 'Outdoor', parent_id: 2}
console.log(getHierarchy(lookup, item))
You would have to write an appropriate lookup function, presumably using this.Database.from(...). You might also want to simplified version that bakes in your lookup function, in which case, you might write
const getAncestry = (item) => getHierarchy(lookup, item)
If, as seems more likely, your lookup is asynchronous, then that will affect getHierarchy and how you call it. Here's one possibility:
const getHierarchy = async (lookup, child) => {
const {id, name, parent_id} = await lookup(child) ||
{id: null, name: null, parent_id: 0}
return parent_id == 0
? {id, name, parent_id}
: {...{id, name}, ...{parent: await getHierarchy(lookup, {parent_id})}}
}
const items = [
{id: 1, name: 'Residential', parent_id: 5},
{id: 2, name: 'Furniture', parent_id: 1},
{id: 3, name: 'Other', parent_id: 0},
{id: 4, name: 'FooBar', parent_id: 3},
{id: 5, name: 'Stuff', parent_id: 0}
]
const lookup = async child => new Promise(
(resolve, reject) => setTimeout(
() => resolve(items.find(item => item.id == child.parent_id)),
1000
)
)
const getAncestry = async item => getHierarchy(lookup, item)
const item = {id: 65, name: 'Outdoor', parent_id: 2}
getAncestry(item).then(console.log)
Note the change in how you call the function. You need to call .then() on the resulting promise to get any useful behavior.

Categories

Resources