Nested list in component from array of objects - javascript

I'm searching for a little help/advice. I had a task to create a multiple nested list from array of object. I did this, got a expected result, but the problem was my code was very complicated and not clean for sure, because i did it by mapping, filtering, and again mapping, mapped arrays. This give me a lot of code, and i am pretty sure you can do it a lot of easier, that's why i am searching for help. I am using react(18.2.0), but even good methods for situations like that in vanilla js will be very helpfull for me.
So there is one json file with a lot of data, i give a example because there is like Array[500+] object inside.
"data": [
{
"categoryId": 1,
"name": "Football",
"lvl": 1,
"parent": 0,
},
{
"categoryId": 2,
"name": "Basketball",
"lvl": 1,
"parent": 0,
},
{
"categoryId": 3,
"name": "Bundesliga",
"lvl": 2,
"parent": 1,
},
{
"categoryId": 4,
"name": "NBA",
"lvl": 2,
"parent": 2,
},
{
"categoryId": 5,
"name": "Wizzards",
"lvl": 3,
"parent": 4,
},
{
"categoryId": 6,
"name": "Lakers",
"lvl": 3,
"parent": 4,
},
.....and more
If parent === categoryId it means that it's children.
So the result component should give something like that:
- Football
- Bundesliga
- Basketball
- NBA
- Wizzards
- Lakers
I will be happy if you give me some good practices, advices about situations like that. Should i use recursion or what? :)

If you wanted to write this recursively, you could write it something like this:
const recursive = function(data, node, current, max) {
if(current > max) {
return {};
}
data.forEach( d => {
if(!node.children) {
node.children = [];
}
if(d.lvl === current && (d.parent === node.categoryId || current === 1)) {
node.children.push(d);
recursive(data, d, current+1, max)
}
});
}
let newObj = {};
let highestLevel = 1;
data.forEach(d => {
if(d.lvl > highestLevel) {
highestLevel = d.lvl;
}
})
recursive(data, newObj, 1, highestLevel)
I wrote this in playcode.io so that you can see it working: https://playcode.io/926085
The output is the entire objects, nested. But you can then print just the names from the resulting nested structure.
I don't think that this solution is the most optimal one, as far as time complexity goes. But it's a recursive example of how to solve the problem.
I'm open to someone optimizing this solution, as I am also interested in this problem.
UPDATE
I had a friend work on a solution. He optimized so that I think its O(n): https://playcode.io/926145/
const f = (input, map = {}) => {
input.forEach(d => {
const me = map[d.categoryId]
if(!me) {
map = {...map, [d.categoryId]: [] }
}
const siblings = map[d.parent]
if(siblings) {
map = {...map, [d.parent]: [...siblings, d]}
} else {
map = {...map, [d.parent]: [d]}
}
})
return map
}
const print = (map, input, toPrint, indent = 0) => {
toPrint.forEach(p => {
const ind = " ".repeat(indent)
console.log(`${ind} - ${p.name}`)
print(map, input, map[p.categoryId], indent + 2)
})
}
const map = f(data.data)
print(map, data.data, map[0])

Related

How to convert a method to recursive function in Javascript?

I created a submenu from the first keys of the "res" data before the dot. In this submenu, I showed the keys with the same name in the "res" data only once. ie: "user", "department" and "project" appear as just a submenu item. But when I click on them, other keys with the same name do not appear once. For example: When I click on the "project" item, 5 "type" submenus are opened. And I need to do the same for the "id" key, too. How can I solve this?
var res = {
"user.name.firstname": "firstname",
"user.name.lastname": "lastname",
"department.id1": 1,
"department.id2": 2,
"project.name": "project",
"project.type.name": "project1",
"project.type.id.first3": "321",
"project.type.id.last3" : "789",
"project.type.id.abc": "abc",
}
var myList = []
var myFinalList = []
Object.keys(res).forEach(key => {
var subMenus = key.split(".")
subMenus.forEach(subMenu => {
var newObject = {}
newObject["id"] = subMenu
newObject["content"] = subMenu
myList.push(newObject)
})
for (var i = myList.length - 1; i >= 0; i--) {
if (i - 1 !== -1) {
//console.log(i - 1)
myList[i - 1]["submenu"] = [myList[i]]
}
}
myFinalList.push(myList[0])
//console.log(myList)
myList = []
})
var mySet = new Set()
myFinalList.forEach(obj => {
mySet.add(obj["id"])
})
var veryFinalList = []
mySet.forEach(key => {
var eachKeyObject = myFinalList.filter(sub => sub.id === key)
var sourceObject = eachKeyObject[0]
if(eachKeyObject.length > 1){
for(var i = 1; i < eachKeyObject.length; i++){
sourceObject["submenu"] = sourceObject["submenu"].concat(eachKeyObject[i]["submenu"])
}
}
veryFinalList.push(sourceObject)
console.log(veryFinalList)
})
And my output should be like this:
output= {
"user": {
"name": {
"firstname": "firstname",
"lastname": "lastname",
}
},
"department": {
"id1":1,
"id2":2,
},
"project": {
"name": "project",
"type": {
"name": "project1",
"id": {
"first3": "321",
"last3" : "789",
"abc" : "abc"
}
}
}
}
Here you go:
var res = {
"user.name.firstname": "firstname",
"user.name.lastname": "lastname",
"department.id1": 1,
"department.id2": 2,
"project.name": "project",
"project.type.name": "project1",
"project.type.id.first3": "321",
"project.type.id.last3" : "789",
"project.type.id.abc": "abc",
}
let finalObj = { };
for(let key in res) {
let value = res[key];
let keyArr = key.split('.');
// Magic happens here (https://stackoverflow.com/a/68860247/2284136)
keyArr.reduce((acc, key, i) => {
if (i < keyArr.length - 1) {
if (!acc[key]) acc[key] = {};
}
else {
acc[key] = value;
}
return acc[key];
}, finalObj)
}
console.log(finalObj);
I used this answer for the reduce implementation, which allows us to put data deep into an object path. We just have to split the keys into and array first, like "user.name.firstname" => ["user", "name", "firstname"] and feed that to the reduce function with the value of res["user.name.firstname"].
Regarding your actual question about "converting a method to recursive function", I did try something like that at first, until I realized we don't need to do that here.
Also for a future reference, while recursive functions are great for some specific problems, the rule of thumb in general is to avoid having to use them if you don't absolutely have to. They are hard to read and understand, hard to debug and can lead to all kinds of wacky problems.
And regarding what you had tried to do to solve this. I think it's a great try and that is exactly what a great programmer should do when running into trouble! I love reading code like that because I can see the rabbit hole of frustration you were digging yourself into when trying to solve the problem. While it is indeed greatly frustrating, it also just as great learning experience. I personally think that when you find yourself from the bottom of that hole, still without a solution, that is the best time to ask for help, like you did!

Transform data from flat into a nested tree structure based on value of indent attribute

We have an array similar to this:
const pages= [
{
"name": "Hello World",
"code": "hello-world",
"indent": 0,
"subpages": null
},
{
"name": "What is going on?",
"code": "what-is-going-on",
"indent": 1,
"subpages": null
},
{
"name": "My Page",
"code": "my-page",
"indent": 0,
"subpages": null
}
]
And we want to nest it so it looks like this:
"data": {
"hello-world": {
"name": "Hello World",
"subpages": {
"what-is-going-on": {
"name": "What is going on?",
"subpages": {}
}
}
},
"my-page": {
"name": "My Page",
"subpages": {}
}}}
So far, I was able to make it work, but it fails when there are MORE OBJECTS with GREATER INDENTS, or just more indents greater than 1 in a row.
This is the code I came up with
var arr = []
for (let i=0; i<pages.length; i++) {
if (pages[i].indent==0) {
arr.push(pages[i]);
}
else {
arr[i-1].children=pages[i]
}
}
Its hard to admit it, but I feel like this approach will not work with more data - greater indents. I don't really know where should my solution be heading from.
What do you think would work?
Thank you for your time.
You could do this using reduce method and use an array to keep indent levels.
const pages = [{"name":"Hello World","code":"hello-world","indent":0,"subpages":null},{"name":"What is going on?","code":"what-is-going-on","indent":1,"subpages":null},{"name":"My Page","code":"my-page","indent":0,"subpages":null}]
const result = {}
const levels = [result]
pages.reduce((r, {name, code, indent}) => {
const subpages = {}
r[indent][code] = {name, subpages}
r[indent + 1] = subpages
return r
}, levels)
console.log(result)

Javascript - Get occurence of json array with ESLINT 6

I can't set up an algo that counts my occurrences while respecting ESlint's 6 standards in javascript.
My input table is :
[
{
"id": 2,
"name": "Health",
"color": "0190fe"
},
{
"id": 3,
"name": "Agriculture",
"color": "0190fe"
},
{
"id": 1,
"name": "Urban planning",
"color": "0190fe"
},
{
"id": 1,
"name": "Urban planning",
"color": "0190fe"
}
]
And i want to get :
{"Urban planning": 2, "Health": 1, ...}
But that does not work with ESLINT / REACT compilation...
This is my code :
const jsonToIterate = *'MyPreviousInputJson'*
const names = []
jsonToIterate.map(item => (names.push(item.name)))
const count = []
names.forEach(item => {
if (count[item]){
count.push({text: item, value: 1})
} else {
count.forEach(function(top){top.text === item ? top.value =+ 1 : null})
}
})
Thank you so much
Well, you want an object in the end, not an array, so count should be {}. I also wouldn't use map if you're not actually returning anything from the call. You can use reduce for this:
let counts = topicsSort.reduce((p, c, i, a) => {
if (!p.hasOwnProperty(c.name)) p[c.name] = 0;
p[c.name]++;
return p;
}, {});
I'm half exppecting someone to close this as a duplicate because all you've asked for is a frequency counter. But here's an answer anyway:
const jsonToIterate = *'MyPreviousInputJson'*;
const names = {};
jsonToIterate.map(obj => {
if(obj.name in names){
names[obj.name]++
}
else{
names[obj.name] = 1;
}
})

Create Parent-Child tree JSON with IDs not following each other

I'm trying to create a tree with database records,
at first I used the following code :
Create Parent-Child tree JSON
Which worked fine, but since I can Insert and Delete nodes of the tree in my database, the function isn't working anymore.
As you can see, now I can have the first Element with for Id 90 and childrens width smaller Ids.
var arry = [{
"parentId": null,
"moduleId": 90
},
{
"parentId": 1,
"moduleId":65
},
{
"parentId": 1,
"moduleId": 91
},
{
"parentId": 65,
"moduleId": 66
},
{
"parentId": 66,
"moduleId": 79
},
{
"parentId": 90,
"moduleId": 1
}
];
fiddle:
https://jsfiddle.net/1c20hb7w/
Of course, I can't change IDs in my database (It would be too simple).
So I would like to know how to makes everything work, if you have a track to help me..
thanks in advance!
For a more complicated tree I'd suggest a recursive approach, as example:
var arry=[{parentId:null,moduleId:90},{parentId:1,moduleId:65},{parentId:1,moduleId:91},{parentId:65,moduleId:66},{parentId:66,moduleId:79},{parentId:90,moduleId:1}];
function recursiveTree(array) {
function getChildren(parents, input) {
return parents.map(parent => {
const children = input.filter(x => x.parentId === parent.moduleId);
parent.children = children;
if(children.length === 0) {
return parent;
} else {
parent.children = getChildren(children, input);
return parent;
}
})
}
const roots = array.filter(x => x.parentId === null);
return getChildren(roots, array);
}
var r = recursiveTree(arry)
console.log('array', r);
console.log('result', JSON.stringify(r))

Simpler way to retrieve value from array

Is there a simpler way to retrieve the value "TestProject" in the JSON response rather than using a for loop in my code?
[
{
"Id": "9ac44c1d-0066-47aa-a2a2-a9b90109b0a5",
"Group": null,
"DataFields": [
{
"Header": "ProjectID",
"Value": "TestProject"
},
{
"Header": "uui_ConfigPack",
"Value": "75e8ce5a-7ae0-41ca-86f0-aca1e7158073"
}
],
"HasDocuments": null
}
]
var projResults = JSON.parse(responseBody);
var projNumber = 1;
dataProjectId = projResults[projNumber].Id;
projName = 'Not Found';
for (i = 0; i < projResults[projNumber].DataFields.length; i++)
{
if(projResults[projNumber].DataFields[i].Header == "ProjectID")
{
projName = projResults[projNumber].DataFields[i].Value;
}
}
It looks like you're trying to find an object in an array, for which the most idiomatic method to use is Array.prototype.find:
var projResults = [
{
"Id": "9ac44c1d-0066-47aa-a2a2-a9b90109b0a5",
"Group": null,
"DataFields": [
{
"Header": "ProjectID",
"Value": "TestProject"
},
{
"Header": "uui_ConfigPack",
"Value": "75e8ce5a-7ae0-41ca-86f0-aca1e7158073"
}
],
"HasDocuments": null
}
];
var projNumber = 0;
const foundObj = projResults[projNumber].DataFields.find(({ Header }) => Header === 'ProjectID');
const projName = foundObj ? foundObj.Value : 'Not Found';
console.log(projName);
You can use higher order functions like map filter reduce etc.
to avoid for-loops.
here is a 1 liner:
var obj = [
{
"Id": "9ac44c1d-0066-47aa-a2a2-a9b90109b0a5",
"Group": null,
"DataFields": [
{
"Header": "ProjectID",
"Value": "TestProject"
},
{
"Header": "uui_ConfigPack",
"Value": "75e8ce5a-7ae0-41ca-86f0-aca1e7158073"
}
],
"HasDocuments": null
}
]
console.log(obj.map(i => i.DataFields).flat(1).find(i => i.Header === 'ProjectID').Value);
As stated by CertainPerformance and vlaz. This will lead to bad performance because of multiple iterations.
But if you like a declarative coding style you can use RxJS
in which you can do something like:
var obs$ = from(obj);
obs$.pipe(
flatMap(i => i.DataFields),
filter(i => i.Header === 'ProjectID' )),
pluck('Value')
).subscribe(console.log);
which basically does the same thing but in a more performant way.
I would suggest reading about Array methods like:
map
filter
reduce
...
the functions will help you manage your arrays in an efficient way, it will make your code looks cleaner, and easy to read, you will find more information about it here:
https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/map
And i do suggest the 'javascript 30' of wesbos, he has a wonderful video about it :)

Categories

Resources