Counting the highest level of nested Arrays inside Object - javascript

I have a problem with a task, I have to count the highest level in a structure which looks like this:
let node = {
age: 23,
name: "christian",
children: [{
age: 25,
name: "michael",
children: [{
age: 33,
name: "Johann",
children: [{
age: 45,
name: "Christiaaann",
}]
}]
}, {
age: 90,
name: "Monika",
children: [{
age: 10,
name: "WHATEVER",
}]
}]
};
The level of the first subtree would be 3 as it contains 3 nested children arrays.
The right level would be 2 as it contains 2 nested children arrays.
I tried to solve it recursively, and of course I know that I have to count the level of each subtree and then check it with a condition if it is greater than the previous maximum.
My problem is that I don't know where to count it up in the recursive call.
This is my solution, so far
let node = {
age: 23,
name: "christian",
children: [{
age: 25,
name: "michael",
children: [{
age: 33,
name: "Johann",
children: [{
age: 45,
name: "Christiaaann",
}]
}]
}, {
age: 90,
name: "Monika",
children: [{
age: 10,
name: "WHATEVER",
}]
}]
};
let level_count;
let max_count = 0;
function level_counter(obj, func) {
level_count = 0;
func(obj);
console.log("INSIDEFUNCTION", level_count);
if (obj.children) {
level_count++;
obj.children.forEach(function(child) {
console.log(child);
level_counter(child, func);
});
if (level_count > max_count) {
max_count = level_count;
}
}
}
function tree_get_levels(root) {
level_counter(root, function(obj) { });
console.log("DOWNONE", level_count);
return 0;
}
let result = tree_get_levels(node);
console.log(result);

I tried to solve it recursively, and of course I know that I have to count the level of each subtree and then check it with a condition if it is greater than the previous maximum.
My problem is that I don't know where to count it up in the recursive call.
You need to use the return value of the recursive call, adding one for the level making the call to it. Separately, always avoid global variables when you're trying to write a recursive solution; every level of recursion sees the same values for those variables, and assigning to them won't work correctly. Stick to variables defined within the recursive function, and (again) report values back up the stack by returning them.
See inline notes in the code:
let node = {
age: 23,
name: "christian",
children: [{
age: 25,
name: "michael",
children: [{
age: 33,
name: "Johann",
children: [{
age: 45,
name: "Christiaaann",
}]
}]
}, {
age: 90,
name: "Monika",
children: [{
age: 10,
name: "WHATEVER",
}]
}]
};
function countLevels(obj, func) {
// No levels so far
let levelCount = 0;
func(obj); // Not sure what this function is for
// If there are any children...
if (obj.children) {
// There are, so we've already gone one level down
for (const child of obj.children) {
// Get levels starting at this child, then add one because we've
// already gone one level down
const levelsFromChild = countLevels(child, func) + 1;
// Remember it if it's higher than the maximum we've seen
if (levelCount < levelsFromChild) {
levelCount = levelsFromChild;
}
// Or that could be replaced with:
// levelCount = Math.max(levelCount, countLevels(child, func) + 1);
}
}
// I added this so you could see the levels count starting from each object
console.log(`${levelCount} level(s) from ${JSON.stringify(obj)}`);
// Return the levels found from this object
return levelCount;
}
// No need for a separate wrapper function, just call the counter directly
let result = countLevels(node, function func(obj) { });
console.log(`Deepest: ${result}`);
.as-console-wrapper {
max-height: 100% !important;
}

You can try this:
function highestDepth(obj) {
let depth = 0;
if (Array.isArray(obj.children) && obj.children.length) {
depth += 1;
let subDepth = 0;
for (const child of obj.children) {
subDepth = Math.max(subDepth, highestDepth(child));
}
depth += subDepth;
}
return depth;
}
console.log(highestDepth(node));

A simple recursive version first checks whether the node has children. If it doesn't, we simply return 0; if it does, we recur over each of its children, and take the maximum of those (or 0 if the children array is empty) and add 1. It looks like this:
const maxDepth = (node) => 'children' in node
? 1 + Math .max (0, ... node .children .map (maxDepth))
: 0
const node = {age: 23, name: "christian", children: [{age: 25, name: "michael", children: [{age: 33, name: "Johann", children: [{age: 45, name: "Christiaaann"}]}]}, {age: 90, name: "Monika", children: [{age: 10, name: "WHATEVER"}]}]}
console .log (maxDepth (node))
The 0 passed as the first parameter to Math .max is necessary because Math .max () (with no parameters) returns -Infinity. It's an interesting exercise to try to figure out why that is.

Here is an iterative solution using object-scan. This solution will work even when you are dealing with very deeply nested object hierarchies.
.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 node = { age: 23, name: 'christian', children: [{ age: 25, name: 'michael', children: [{ age: 33, name: 'Johann', children: [{ age: 45, name: 'Christiaaann' }] }] }, { age: 90, name: 'Monika', children: [{ age: 10, name: 'WHATEVER' }] }] };
const fn = objectScan(['**{children[*]}'], {
rtn: 'count', // for performance
beforeFn: (state) => {
state.context = { depth: 0 };
},
filterFn: ({ context, depth }) => {
context.depth = Math.max(context.depth, depth);
},
afterFn: ({ context }) => context.depth / 2
});
console.log(fn(node));
// => 3
</script>
Disclaimer: I'm the author of object-scan

Related

How to improve time complexity of this snippet?

I am trying to improve the time complexity and quality of the code snippet below.
I am iterating through one array to check if the element this array exists in the object, should this be true it should return the name matching the element id in the object.
how can I do this without having a nested loop?
Can someone tell me what I can do to make this algo better, please?
Thank you all in advance.
let genres = [28, 12, 878];
data = {
genres: [
{
id: 28,
name: 'Action',
},
{
id: 12,
name: 'Adventure',
},
{
id: 16,
name: 'Animation',
},
{
id: 35,
name: 'Comedy',
},
{
id: 80,
name: 'Crime',
},
{
id: 99,
name: 'Documentary',
},
{
id: 18,
name: 'Drama',
},
{
id: 10751,
name: 'Family',
},
{
id: 14,
name: 'Fantasy',
},
{
id: 36,
name: 'History',
},
{
id: 27,
name: 'Horror',
},
{
id: 10402,
name: 'Music',
},
{
id: 9648,
name: 'Mystery',
},
{
id: 10749,
name: 'Romance',
},
{
id: 878,
name: 'Science Fiction',
},
{
id: 10770,
name: 'TV Movie',
},
{
id: 53,
name: 'Thriller',
},
{
id: 10752,
name: 'War',
},
{
id: 37,
name: 'Western',
},
],
};
const getGenreName = () => {
let result = [];
for (let genre of data.genres) {
//console.log("genre", genre.name)
for (let id of genres) {
//console.log('id',genres[i])
if (id === genre.id) result.push(genre.name);
}
}
console.log(result);
};
getGenreName();
You can use reduce and includes as others have already shown. This will make the code a bit cleaner, but not change the overall runtime complexity. To improve runtime complexity you may need to use a different data structure.
For instance instead of
let genres = [1,2,3,4];
as a simple array, you could use a Set, which has a better lookup performance.
let genres = new Set([1,2,3,4]);
Then you can use this as follows
let result = data.genres
.filter(g => genres.has(g.id))
.map(g => g.name);
and won't need any explict for loops
The simplest improvement would probably be converting genres to a Set https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
and use the has method to check if each id in the data is a member of the set of chosen genres.
You can also convert the data to a map with the ids as the keys in order to look up by id quickly instead of looping, but that is only faster if the data is reused many times.
JavaScript #reduce in the example outlined below would have O(n) time complexity. This only loops through the array once. We could use filter, and map but it would result in us having to loop through the array twice.
const getGenreName = () => {
const genreSet = new Set(genres);
return data.genres.reduce((accumulator, { id, name }) => {
if (genreSet.has(id)) accumulator.push(name);
return accumulator;
}, []);
};
console.log(getGenreName()); // [ 'Action', 'Adventure', 'Science Fiction' ]
We are initializing the reducer to start with the array [], or an empty array, and then checking to see if the genre property of the object is included in the genres array, if it isn't, return the accumulator, if it is, append it to the end of the accumulator and return it.
You wanted this in one loop, so here it is:
let result = [];
data.genres.forEach(function (e) {
if (genres.includes(e.id)) result.push(e.name);
});
console.log(result);
In case you were wondering about forEach, here's a very good reference: https://www.w3schools.com/jsref/jsref_foreach.asp
The current time complexity is O(MN) where M is the length of data.genres and N is the length of genres.
Time complexity in JavaScript depends on which engine you use, but in most cases you can use a Map to reduce this time complexity to O(max{N,M}):
const getGenreName = () => {
const dataGenresMap = new Map( // O(M)
data.genres.map(({id,...params}) => [id,params]) // O(M)
)
let result = []
for (let id of genres) { // O(N)
if (dataGenresMap.has(id)) result.push(dataGenresMap.get(id).name) // O(1)
}
console.log(result)
}
If you might be doing this more than once then I'd recommend using a Map. By creating a hash map, retrieving genre names per id is much more performant.
let genres = [28, 12, 878];
data = {
genres: [
{
id: 28,
name: 'Action',
},
{
id: 12,
name: 'Adventure',
},
{
id: 16,
name: 'Animation',
},
{
id: 35,
name: 'Comedy',
},
{
id: 80,
name: 'Crime',
},
{
id: 99,
name: 'Documentary',
},
{
id: 18,
name: 'Drama',
},
{
id: 10751,
name: 'Family',
},
{
id: 14,
name: 'Fantasy',
},
{
id: 36,
name: 'History',
},
{
id: 27,
name: 'Horror',
},
{
id: 10402,
name: 'Music',
},
{
id: 9648,
name: 'Mystery',
},
{
id: 10749,
name: 'Romance',
},
{
id: 878,
name: 'Science Fiction',
},
{
id: 10770,
name: 'TV Movie',
},
{
id: 53,
name: 'Thriller',
},
{
id: 10752,
name: 'War',
},
{
id: 37,
name: 'Western',
},
],
};
const genreById = new Map ();
data.genres.forEach(({id, name}) => genreById.set(id, name));
const pushMapValueIfTruthy = map => array => key => {
const val = map.get(key);
if (val) {
array.push(val);
}
};
/** function that takes an array, then id, and pushes corresponding name (if exists) into the array. */
const pushGenreNaneIfExists = pushMapValueIfTruthy(genreById);
const getGenreNames = (ids) => {
result = [];
ids.forEach(pushGenreNaneIfExists(result));
return result;
};
console.log(getGenreNames(genres));

find object with greatest number of elements

I have written a function which will return a name with the greatest number of children from a contact list (array of objects), but it is limited to the number of elements I include in the if statement. How can I make it go over all the elements in this contact list, regardless of size? I tried to make a for of loop, but it won't let me sort through elements using a counter "I", returning an error.
Thanks in advance!
let contacts = [
{
name: 'John',
children:
[
{ name: 'Mary', age: 11 }
]
}, {
name: 'Franklin',
children:
[
{ name: 'Susy', age: 7 },
{ name: 'Tom', age: 5 }
]
}
];
function p_w_m_children(arr){
let person_w_most_children = "";
arr.sort((elem_1, elem_2) => {
if (elem_1.children > elem_2.children){
person_w_most_children = elem_1.name;
}else{
person_w_most_children = elem_2.name;
}
});
return person_w_most_children;
}
console.log("The person who has most children is " + p_w_m_children(contacts)+ ".");
To find the person with the most children using arr.sort, you can use the following:
arr.sort((elem_1, elem_2) => {
return elem_1.children.length - elem_2.children.length
}
The key points being that you compare the .length of the arrays (rather than just comparing the arrays themselves) and that the .sort callback returns a value that can be sorted. The return is positive when elem_1.children.length > elem_2.children.length, negative when elem_1.children.length < elem_2.children.length and 0 when they are equal. This means the sort function can order the array correctly.
Then, once the array is sorted you can simply get the last element in the sorted array (the element with the largest value).
Edit for Clarification
let sortedArr = arr.sort((elem_1, elem_2) => {
return elem_1.children.length - elem_2.children.length;
};
return sortedArr[sortedArr.length - 1].name;
You can loop through the array and keep a record of the contact who has the most children. Replace that record with the current one in the loop if the number of children is greater.
Then return that contact when the loop is finished.
let contacts = [{
name: 'John',
children: [{
name: 'Mary',
age: 11
}]
}, {
name: 'Franklin',
children: [{
name: 'Susy',
age: 7
},
{
name: 'Tom',
age: 5
}
]
}];
function p_w_m_children(arr) {
let person_w_most_children;
contacts.forEach(contact => {
if (!person_w_most_children || contact.children.length > person_w_most_children.children.length) {
person_w_most_children = contact;
}
})
return person_w_most_children;
}
console.log("The person who has most children is " + p_w_m_children(contacts).name + ".");
Same as above without using forEach :
let contacts = [{
name: 'John',
children: [{
name: 'Mary',
age: 11
}]
}, {
name: 'Franklin',
children: [{
name: 'Susy',
age: 7
},
{
name: 'Tom',
age: 5
}
]
}];
function p_w_m_children(arr) {
let person_w_most_children;
for (let i = 0; i < contacts.length; i++) {
if (!person_w_most_children || contacts[i].children.length > person_w_most_children.children.length) {
person_w_most_children = contacts[i];
}
}
return person_w_most_children;
}
console.log("The person who has most children is " + p_w_m_children(contacts).name + ".");

Create a tree from a list of strings - javascript

I have an array of objects and I want to create a view tree. But I have a problem with creating. My name Test1/Test2 and Test1/Test2/Test3 (id 147) was missed in final tree.
My snippet:
let arr = [{id: 145, name: "Test1/Test2", public: false},
{id: 146, name: "Test1/Test2/Test3", public: false},
{id: 147, name: "Test1/Test2/Test3", public: false},
{id: 148, name: "Test1/Test2/Test4", public: false}];
let result = [];
let level = { result };
arr.forEach((path) => {
path.name.split("/").reduce((r, name, i, a) => {
if (!r[name]) {
r[name] = { result: [], id: path.id };
r.result.push({ name, children: r[name].result });
}
return r[name];
}, level);
});
console.log(result)
Expected result:
[
{
name: "Test1",
children: [
{
name: "Test2",
children: [],
id: 145
},
{
name: "Test2",
children: [
{
name: "Test3",
children: [],
id: 146
},
{
name: "Test3",
children: [],
id: 147
},
{
name: "Test4",
children: [],
id: 148
},
],
},
],
},
];
If I understand well, the number of leaves in your tree should equal the number entries in the input array. So a leaf would never get any children. This is what made you give "Test1" two children, even though all paths have "Test2" as the next part: one child for a leaf, and another functioning as internal node.
By consequence, leaves don't really need a children property, as that children array would always remain empty.
It is clear that the last element of a path needs to be processed a bit differently. That part should always result in the creation of a new node in the tree. The other parts can reuse a (non-leaf) node, if one is available.
This leads to the following change in your code:
let arr = [
{id: 145, name: "Test1/Test2", public: false},
{id: 146, name: "Test1/Test2/Test3", public: false},
{id: 147, name: "Test1/Test2/Test3", public: false},
{id: 148, name: "Test1/Test2/Test4", public: false}
];
let result = [];
let level = { result };
arr.forEach(({name, id}) => { // destructure
let parts = name.split("/");
name = parts.pop(); // don't pass the last part through reducer
parts.reduce((r, name, i) => {
if (!r[name]) {
r[name] = { result: [] };
r.result.push({ name, children: r[name].result });
}
return r[name];
}, level).result.push({ name, id }); // add last part here
});
console.log(result);

I'm trying to figure out how to add the values in an object using a function

Here's the code. I left the function block blank because I'm stumped
function getSummedAge(people) {
}
const examplePeopleArray = [
{ name: 'John', age: 10 },
{ name: 'Jack', age: 20 },
{ name: 'Jane', age: 25 }
];
console.log(getSummedAge(examplePeopleArray));
Checkout the Array.reduce method! It's purpose is to basically reduce an array into a single value, which I believe is what you're trying to do here: Array.reduce() MDN
Here's a little example of how you might use it in this context
function sumAllApplesIn(baskets){
return baskets.reduce((accumulator, currentBasket) => accumulator + currentBasket.apples, 0)
}
const baskets = [
{ basket: "one", apples: 10},
{ basket: "two", apples: 15},
{ basket: "three", apples: 30}
];
console.log(sumAllApplesIn(baskets));
You simply need to loop through the people array, grab the 'person' object, and then access the age key. Sum those up and voila
function getSummedAge(people) {
let summedAge = 0;
for (let pdx = 0, plen = people.length; pdx < plen; pdx++) {
let person = people[pdx];
summedAge += person.age;
}
return summedAge
}
const examplePeopleArray = [
{ name: 'John', age: 10 },
{ name: 'Jack', age: 20 },
{ name: 'Jane', age: 25 }
];
console.log(getSummedAge(examplePeopleArray));

Finding object with lowest value for some key, in Javascript

In my Javascript program, I have a list of Person objects.
For example
[
"Michael": {
"age": 45,
"position": "manager",
...
},
"Dwight": {
"age": 36,
"position": "assistant manager",
...
},
....
]
I want to find the youngest Person.
I've accomplished this by creating two arrays: one of all the Persons and one of all their ages, and getting the index of the lowest age and applying it to the first array. Like:
var arrayOfPersons = [persons[0], persons[1], ....];
var arrayOfAges = [persons[0].age, persons[1].age, ....];
var min = arrayOfAges.indexOf(Math.max.apply(Math, arrayOfAges));
var youngestPerson = arrayOfPerson[min];
The problem with this is it is inefficient doesn't seem like the best way. Also it doesn't deal with the fact that there may be a tie for youngest.
Does anyone know of a more native, simpler way to do this?
You can sort the persons array by age property and pick first one:
var persons = [
{ name: 'Michael', age: 45, position: 'manager' },
{ name: 'Dwight', age: 36, position: 'assistant manager' },
{ name: 'Foo', age: 99, position: 'foo' },
{ name: 'Bar', age: 37, position: 'bar' }
];
persons.sort(function(a, b) {
return a.age > b.age;
});
console.log('Youngest person: ', persons[0]);

Categories