Find deep nested array depth - javascript

I have a nested array. Like below:
I want to find the depth of this nested array, which means the child element has most deep nested children.
let arr = [
{
name: 'tiger',
children: [{
name: 'sinba',
children: [{
name: 'cute',
children: []
}]
}]
},
{
name: 'lion',
children: []
}
]
In this case, the depth is 3, the tiger has 3 level. So the depth is 3
How could i achieve this? I try to use recursive, but don't know how to find the element which
has most nested children.
Thanks in advance.

Assuming that there are no circular references, you could try something like this
let arr = [{
name: 'tiger',
children: [{
name: 'sinba',
children: [{
name: 'cute',
children: []
}]
}]
},
{
name: 'lion',
children: []
}
]
function count(children) {
return children.reduce((depth, child) => {
return Math.max(depth, 1 + count(child.children)); // increment depth of children by 1, and compare it with accumulated depth of other children within the same element
}, 0); //default value 0 that's returned if there are no children
}
console.log(count(arr))
Our function would not work if there were some circular references, so there might be a need to adjust it accordingly. Detecting circular references is a whole ordeal. If nothing is done about it, the function will throw a Maximum call stack size exceeded error.
In order to handle it without any additional functionality implementation you could use already existing native JSON.stringify to do so. The stringify option will throw an exception only if you try to serialize BigInt values which we can handle ourselves or when objects are cyclic, which is excatly what we wanted.
let arr = [{
name: 'tiger',
children: []
}]
function testCircular(arr){
try {
BigInt.prototype.toJSON = function() { return this.toString() } // Instead of throwing, JSON.stringify of BigInt now produces a string
JSON.stringify(arr);
return false;
}
catch (e) {
// will only enter here in case of circular references
return true;
}
}
function count(children) {
if (testCircular(children)) return Infinity;
return children.reduce((depth, child) => {
return Math.max(depth, 1 + count(child.children)); // increment depth of children by 1, and compare it with accumulated depth of other children within the same element
}, 0); //default value 0 that's returned if there are no children
}
console.log(count(arr)) // normally counting
arr[0].children = arr; // creates circular reference
console.log(count(arr)) // counting for circular

Related

Javascript : Modify object and add new index to a property

My object is something like:
let items =
[
{
"creationTimeStamp": "2022-05-31T17:04:28.000Z",
"modifiedTimeStamp": "2022-05-31T17:04:28.000Z",
"locations": [
{
"id": "5ao",
"name": "Store1"
}
],
"typeId": "Lead"
}
]
I am trying to push the following object into the locations property:
{
"id": "9a0",
"name": "Store2"
}
I have tried doing
items1 = [];
for (var i = 0; i < items.length; i++) {
items1.id = "9a0";
items1.name = "Store2";
//9 is some static index value added
Object.assign({9 : items1}, items[i].locations);
}
If I console(Object.assign({9 : items1}, items[i].locations)); I can see 2 arrays inside it, but my items locations property is still the same.
My expectation is as below:
[
{
"creationTimeStamp": "2022-05-31T17:04:28.000Z",
"modifiedTimeStamp": "2022-05-31T17:04:28.000Z",
"locations": [
{
"id": "5ao",
"name": "Store1"
},
{
"id": "9a0",
"name": "Store2"
}
],
"typeId": "Lead"
}
]
I also tried to use items[i].locations.push(item1) but then got:
TypeError: Cannot add property 9, object is not extensible
I also tried to assign a new array to items[i].locations, but then got:
TypeError: Cannot assign to read only property 'locations' of object '#'
What can I do to get the desired result?
You seem to expect that the second argument given to Object.assign will be mutated. But it is the first argument that is mutated. That means your .locations is not mutated. Moreover, in comments you indicate that locations cannot be extended and that the property is read-only.
So that means you'll need a complete new object.
Some other remarks:
Don't initialise items1 as an array, since it is supposed to be a plain object.
Declare a variable with const, let or var and avoid implicit global declaration.
It is safer to declare the items1 object inside the loop, so you create a new object each time and don't mutate the same object. For your example code it makes no difference, but it can lead to unexpected behaviour.
As you don't need i for anything else than items[i], and you actually need a complete new structure, use .map instead.
So:
items = items.map(item => {
let obj = {
id: "9a0",
name: "Store2"
};
return {...item, locations: item.locations.concat(obj) };
});
I always think in terms of functions, and of immutability-by-default, so my approach might look like this, with addLocationToAll built atop a simpler addLocation. The code is fairly simple:
const addLocation = (newLoc) => ({locations, ...rest}) =>
({...rest, locations: locations .concat (newLoc)})
const addLocationToAll = (newLoc) => (items) =>
items .map (addLocation (newLoc))
const items = [{creationTimeStamp: "2022-05-31T17:04:28.000Z", modifiedTimeStamp: "2022-05-31T17:04:28.000Z", locations: [{id: "5ao", name: "Store1"}], typeId:"Lead"}]
const newLoc = {id: "9a0", name: "Store2"}
console .log (addLocationToAll (newLoc) (items))
.as-console-wrapper {max-height: 100% !important; top: 0}
items is an array so it must access the first position of the array, which would be the proposed object.
With this, from the proposed object you will extract thelocation attribute and since this is an array, you use the push function to insert the new object
items[0]
// ->
// {
// creationTimeStamp: '2022-05-31T17:04:28.000Z',
// modifiedTimeStamp: '2022-05-31T17:04:28.000Z',
// locations: [ { id: '5ao', name: 'Store1' } ],
// typeId: 'Lead'
// }
I try this:
items[0].locations.push({"id": "9a0", "name": "Store2" })
And now:
items[0]
//->
// {
// creationTimeStamp: '2022-05-31T17:04:28.000Z',
// modifiedTimeStamp: '2022-05-31T17:04:28.000Z',
// locations: [ { id: '5ao', name: 'Store1' }, { id: '9a0', name: 'Store2' }],
// typeId: 'Lead'
// }

Find minimal and maximum values in a tree like object using recursion js

I'm trying to find min and max values in tree like object using recursion, but I don't actualy understand how to find this values. Also my function must be pure and I can't use loops or forEach. Only map, reduce, filter are avalaible. So this is how my data looks like:
const tree = {
children: [
{
children: [
{
children: [],
values: [15.667786122807836]
}
],
values: [35.77483035532576, 1.056418140526505]
},
{
children: [
{
children: [
{
children: [],
values: [67.83058067285563]
}
],
values: [98.89823527559626]
}
],
values: [51.49890385802418, 41.85766285823911]
},
],
values: [6.852857017193847, 28.110428400306265, 51.385186145220494]};
I'm trying to do something like this:
const min = graph => {
if (!graph.children.length && !graph.values.length) return;
if (!graph.children.length && graph.values.length) {
return Math.min(...graph.values);
}
return graph.children.map(el => {
const minValue = Math.min(...el.values);
min(el);
return minValue;
});
};
But this not work well. So guys can anybody explain how the callstack works, maybe give me some good examples, and explain how to solve my problem. Thanks for the help and sorry for the bad English). Oh, and also)) how to get the distance between two nodes in the different depth level?
Usually with homework questions, I would prompt for more dialog with the OP, but as this already has a working, accepted answer, I'll just add a simpler one:
const min = ({values = [], children = []}) =>
Math .min (...values, ... children .map (min))
const tree = {children: [{children: [{children: [], values: [15.667786122807836]}], values: [35.77483035532576, 1.056418140526505]}, {children: [{children: [{children: [], values: [67.83058067285563]}], values: [98.89823527559626]}], values: [51.49890385802418, 41.85766285823911]}, ], values: [6.852857017193847, 28.110428400306265, 51.385186145220494]};
console .log (min (tree))
We just use Math.min, spreading the current values and the results of recursive calls to each of its children into arguments to it. We default both values and children to empty arrays in case either is missing at any node.
Clearly max is a trivial change to this.
I'm not a JS programmer but am always looking to practice. Here's what I came up with:
const tree = {
children: [{
children: [{
children: [],
values: [15.667786122807836]
}],
values: [35.77483035532576, 1.056418140526505]
},
{
children: [{
children: [{
children: [],
values: [67.83058067285563]
}],
values: [98.89823527559626]
}],
values: [51.49890385802418, 41.85766285823911]
},
],
values: [6.852857017193847, 28.110428400306265, 51.385186145220494]
};
function treeMin(graph) {
if (graph.children.length == 0) return Math.min(...graph.values);
return Math.min(...graph.values,
graph.children.reduce((prev, cur) =>
Math.min(prev, treeMin(cur)), Number.MAX_SAFE_INTEGER
));
}
console.log(treeMin(tree));
I gave the function a name to make the recursive calls. The first thing it does is check if there are no children. If not, it just returns the min of the values.
If there are children, return the min of values and the result of calling reduce on the children. Inside reduce, the recursive calls are made.
Note: it does not handle the case where values is empty. This can be easily added.

How to iterate through every object.child in a deeply nested object?

I have following structure:
instance: {
children: [instance, instance ...]
}
As you can see each instance has children array with another instances in it, hence this repetition can go relatively deep until last nested child is reached. I need to iterate through each child of uppermost / given instance and perform a simple if condition. But I can't figure out how to do that as length of children can vary, plus we need to check children of each child and so on...
You can recursively iterate over all instances:
function iterate(instance) {
for (let child of instance.children) {
iterate(child);
}
}
Example:
instance = {
name: 'Parent',
children: [{
name: 'Child 1',
children: []
}, {
name: 'Child 2',
children: [{
name: 'Grandchild 1',
children: []
}]
}]
};
function iterate(instance, f) {
if (f)
f(instance.name);
for (let child of instance.children) {
iterate(child, f);
}
}
iterate(instance, console.log);
Output:
Parent
Child 1
Child 2
Grandchild 1

JavaScript loop nested array

I'm building an array with data received from an API.
The result from the API contains a flat array of all pages on a site. I want to create a new array by making it multidimensional so that a page can have children, which can have their own children etc.
Expected array if I can get this to work:
array =
[{id:1, children:[] } // No children
,{id:2, children:[{id:3, children:[]}]} // One level of children
,{id:4, children:[{id:5, children:[{id:6, children:[]}]}]} // Two levels of children etc...
]
So what I'm doing now when looping through the API data is first checking if the element has children. If it doesn't have any I just append it to the array as it is but with an empty children[] as extra.
If the element has a parent I will get the parentId which I have to search for in my custom array.
if(!element.parent){
array.push(element);
continue;
}
// Now I know this element has a parent. So the fun begins.
var parentId = element.parent.id; // Here I have the parent ID
// Example of a none working append (because I dont know the depth)
for(var i = 0; i < array.length; i++){
if(array[i].id === parentId){
array[i].children.push(element);
}
}
So the question is how to loop through all these possible levels of children to find a match and append it correctly to my custom array?
Perhaps this code sample can lead you in the right direction:
var buildTree = function (arr, parent) {
var result = [];
var subTree = arr.filter((e) => e.parent === parent);
for (var i = 0; i < subTree.length; ++i) {
result[i] = {
...subTree[i],
children: buildTree(arr, subTree[i].id)
}
};
return result;
};
var arr = [{
id: 1,
parent: 0,
name: "item-1"
},
{
id: 2,
parent: 1,
name: "item-1.1"
},
{
id: 3,
parent: 2,
name: "item-1.1.1"
},
{
id: 4,
parent: 2,
name: "item-1.1.2"
},
{
id: 5,
parent: 1,
name: "item-1.2"
},
{
id: 6,
parent: 0,
name: "item-2"
},
];
var result = buildTree(arr, 0);
console.log(result);
The recursive buildTree method is returning each time the subtree from a given parent id (first time is expected to be the root of the tree).
BTW: in recursion, the most important thing you need to care about is, besides what you want your method to do (one level each time), the stop condition for your method (when you won't call it again). In this case when there are no more children (handled by the for loop over the filtered data).

prevent circular reference in Array in Javascript

I have a Datastructure like the following.
array=[
{id:"a",children:[
{id:"b",children:[
{id:"d",children:[]}]},
{id:"c",children:[]}]}
]
If I want to insert the element {id:"e", children:["a","f"] at "c" ("a","e" are no Strings, but copies of the nodes) I want to check, that it exists in the upper Tree and would therefore create a circular reference. So I think I have to reverse walk the array. But as I'm pretty new to Javascript and Node, I have no idea how to do that.
Would it be agood idea, to create an array, I store all the dependencies in? something like this:
[
a:[],
b:[a],
c:[a,b]
d:[a,b]
]
then I could lookup the parent in the array and would see that in c, a and b are allready in dependency
You could use a hash table, if the id is unique.
var array = [{ id: "a", children: [{ id:"b", children: [{ id: "d", children: [] }] }, { id: "c", children: [] }] }],
hash = Object.create(null);
// creating circular reference
array[0].children[1].children.push(array[0]);
array.forEach(function iter(a) {
if (hash[a.id]) {
console.log(a.id, 'circular reference found');
return;
}
hash[a.id] = true;
a.children.forEach(iter);
});
console.log(array);

Categories