Nesting related elements together parent/child - javascript

Let's say I have an array as so with objects:
[
{
"id": "5a97e047f826a0111b754beb",
"name": "Hogwarts",
"parentId": "5c7bf2191d41c810b2ad6186",
"childrenIds": []
},
{
"id": "5c7bf2191d41c810b2ad6186",
"name": "Defense Against The Dark Arts",
"parentId": null,
"childrenIds": [
"5a97e047f826a0111b754beb"
]
}
]
What I'd like to do is a function that returns another array, but this time with only items that don't have a parentID as root elements, and have a children array on them containing their children, so on reaching leaves who have an empty childrenIDs array. (also remove the parent/children id properties)
For the previous input I'd return something like this
[
{
"id": "5c7bf2191d41c810b2ad6186",
"name": "Defense Against The Dark Arts",
"children": [
{
"id": "5a97e047f826a0111b754beb",
"name": "Hogwarts"
}
]
}
]
I can't seem to think of any efficient code for this task, can anyone help?

You could keep a reference to each node by ID in an object and build the tree while you go. Since we may encounter references to entries that we didn't see yet, we'll create stubs in the meantime (consisting only of a children array) and add the rest of their fields later on.
This way we have only a single loop.
It seems you have sort of a vertical double-linked list, saving both the parent ID in the children and the children's IDs in the parent, but we need only one of those to build the tree. We'll use the parent ID stored in each child. (Note that this assumes that your structure is consistent without imbalanced relations or dangling references.)
For simplicity, we create one root node whose children we'll return at the end, so that we don't have to handle the nodes without parent any differently.
Then you could write code like this:
function makeTree (rows) {
// Use a symbol to denote the root ID to avoid clashes with any real IDs
const ROOT = Symbol('ROOT')
// Node index, by ID
// Add a root node from the start
const nodes = { [ROOT]: { children: [] } }
// Helper function to return an existing node or create a stub if not existing
const getNodeOrCreateStub = id => nodes[id] || (nodes[id] = { children: [] })
for (const row of rows) {
// Add current row to index, merging data with existing stub, if any.
// This keeps any existing references in other nodes' children arrays intact
// because Object.assign mutates the first object passed to it and returns the
// same object.
const node = Object.assign(getNodeOrCreateStub(row.id), row)
// Remove unwanted properties.
delete node.parentId
delete node.childrenIds
// Get parent or create parent stub if parent node not already existing
const parent = getNodeOrCreateStub(row.parentId || ROOT)
// Add current node as child to parent
parent.children.push(node)
}
// Return children of root node
return nodes[ROOT].children
}
Note that this code currently also creates empty children arrays in the leaf nodes, differently from your example above. However I think this makes the code simpler because it doesn't have to handle leaf nodes any different, neither in creating the tree nor in reading it later! (You wouldn't have to do children && children.length to check for children, you could always just access children directly.)
To change that and have the result exactly as in your example, you'd change the code as follows:
// Change this...
const getNodeOrCreateStub = id => nodes[id] || (nodes[id] = { children: [] })
// ...to this:
const getNodeOrCreateStub = id => nodes[id] || (nodes[id] = {})
// Also change this...
parent.children.push(node)
// ...to this:
if (!parent.children) parent.children = []
parent.children.push(node)
// ...or to this:
parent.children = [...parent.children || [], node]

Related

How do i traverse a children table that have relationship with multiple tables

How do i traverse the child book table when i pass the grandparent_id or parent_id to look up all the related books both in parent book and child book tables. I'm guessing it is more of a data structure problem. I'm using Activerecord to fetch the data from the database.
Tables structure
grand_parent_category
Id name
2 Math
parent_category
Id name
1 Algebra book
child_category
Id name parent_id. grandparent_id.
1. Calculus book 1 2
The normal way is to traverse the entire child books array and search the grandparent_id in the book column.
it would be the same if i choose parent_id
Example
#child_books = child_books.all()
Im passing the #child_books object to the frontend via Gon
Javascript
gon.child_books.forEach((book) => {
If (book.grandparent_id == chosen_grandparent_id) {
// do something
} else if book.parent_id == chosen_parent_id {
// do something
} else if book.parent_id == children_id
});
The result would be. If I choose grandparent_id book. Math
Grandparent = Math
Parent = Algebra, Calculus
Children = Additional Math, Discrete Math
Math
/
Algebra
/ \
Additional Math Discrete math
But this approach is really slow, if the dataset for child book category is huge let say 5000. Then in order to find the relationship I have to traverse one by one
Another approach I was thinking is to use hash
#child_books = child_books.all()
#child_books.index_by(&:id)
This will give this result
{
1: {id: 1, name: “additional mathematics”, parent_id: 1, grandparent_id: 2 }
2: {id: 2, name: “discrete mathematics”, parent_id: 1, grandparent_id: 2 }
}
But this approach can’t be done if I pass the grandparent_id to search for both parent and children books.
What approach should tackle this relationship problem.
TLDR; You problem is not likely searching through the data but in // do something part of your code.
Like #jad said it's very difficult to tell what you are trying to do. What got my attention was when you said if the dataset for child book category is huge let say 5000 5000 is tiny. It's barely even a spec in most cases.
What follows is likely not a direct solution to your problem.
// Let setup some test data, nothing too crazy
// 100K Child Books
// 10K Parent Books
// 100 Grand Parent Books
const getRandomInt = (max) => Math.floor(Math.random() * Math.floor(max))
let grand_parent_category = ''.padStart(100, ' ').split('').map((e, id) => ({id, name: `math:${id}`}))
let parent_category = ''.padStart(10000, ' ').split('').map((e, id) => ({id, name: `additional mathematics:${id}`, parent_id: getRandomInt(grand_parent_category.length)}))
let child_books = ''.padStart(100000, ' ').split('').map((e, id) => ({id, name: `child mathematics:${id}`, parent_id: getRandomInt(parent_category.length)}))
// Not counting the setup in the processing time but it's' near instant
let times = {}
// Map our data and create child buckets for easy lookup
times.startSetup = new Date()
const grandParentMap = new Map(grand_parent_category.map(ele => [ele.id, {...ele, children: []}]))
// While mapping parent add the parent to the grandParent for o(1) lookup
const parentMap = new Map(parent_category.map(ele => {
if (grandParentMap.has(ele.parent_id)) grandParentMap.get(ele.parent_id).children.push(ele.id)
return [ele.id, {...ele, children: []}]
}))
// While mapping child add the child to the parent for o(1) lookup
const childMap = new Map(child_books.map(ele => {
if (parentMap.has(ele.parent_id)) parentMap.get(ele.parent_id).children.push(ele.id)
return [ele.id, ele]
}))
// get a Grand Parent and all his children and grandchildren
const getGrandParent = (grandParentId) => {
if (!grandParentMap.has(grandParentId)) return
let grandParent = {...grandParentMap.get(grandParentId)} // shallow clone - safe based on the data given
grandParent.grandChildren = []
grandParent.children = grandParent.children.map(parentID => {
if (!parentMap.has(parentID)) return parentID
let parent = {...parentMap.get(parentID)} // shallow clone - safe based on the data given
// iterate over the children and map them into grandParent.grandChildren
parent.children.forEach(childID => grandParent.grandChildren.push(childMap.has(childID) ? {...childMap.get(childID)} : childID)) // shallow clone - safe based on the data given
return parent
})
return grandParent
}
times.finishSetup = new Date()
let grandParentId = 42
times.startSearch = new Date()
let results = getGrandParent(grandParentId)
times.finishSearch = new Date()
console.log(`grandParent ${grandParentId} has ${results.children.length} children and ${results.grandChildren.length} grandchildren!`)
console.log(`Setup took ${times.finishSetup - times.startSetup}ms`)
console.log(`Search took ${times.finishSearch - times.startSearch}ms`)
// Sample run
// grandParent 42 has 97 children and 991 grandchildren!
// Setup took 60ms
// Search took 1ms
If our data set gets bigger we may want to optimize the setup. I tried 10B children on my local system and it does take around 6 seconds. For sub 1M children this quick solution works well.

How to pass values from Child Array to a Parent

My requirement is as follows
In parent component, i am passing an array of Child Components(array can be 1 or more than 1)
As the image shows, a child component consists of elements like, input[type=range], input[type=number], dropdown menu, etc
Parent component has a button
<button>Search Location</button>
When I click on Search button in Parent, I need the value of every single elements in each Child Component,
for eg. structure can be as follows
let finalObj={
child1: {
dropValue: "Room1",
cond: "AND"
},
child2: {
inputVal: 50,
cond: "OR"
},
child[n]: {
rangeVal: 1,
cond: ""
}
}
Also, we can change the value again(before clicking search), and Search button should always pickup, the current set value of each component.
I am not sure how to go ahead with this. Any pointers will be really helpful. Please help
So you need to change an array of components into an object of... well first of all that's a .reduce use.
const almostFinalObj = components.reduce((retval, each, i) => {
retval['child'+i] = each;
return retval;
}, {});
That will give an object like
almostFinalObj = {
child1: component1,
child2: component2,
childN: componentN,
}
Now we can .forEach through it, transforming each child component into whatever format you're looking for. (I'm unclear on that part but maybe you can figure out the rest.)
Object.keys(almostFinalObj).forEach((each, i, array) => {
let component = array[i];
array[i] = {};
component.querySelectorAll('input').forEach(e => {
array[i][e.name] = e.value;
});
});
This assumes the name attribute exists on each element in each child row-component. (As in, each radio button in your example has <input type='radio' name='cond' .... />.) You could use id or even a data-XXX attribute as well instead of e.name.

How to delete references to children of deleted properties

When I delete an object property, I need to remove all references to descendant properties scattered around my application:
const people = {
mary: {
children: [
john: new ChildClass(),
paul: new ChildClass(),
george: new ChildClass()
]
},
homer: {
children: [
bart: new ChildClass(),
lisa: new ChildClass(),
maggie: new ChildClass()
]
}
};
const all_children = [];
/* pseudocode:
foreach people(children) {
foreach children(child) {
all_children.push(child);
}
}
*/
all_children.forEach(child => {
/* does something with mary and homer's children */
});
//////////////////
delete people.mary;
all_children.forEach(child => {
/* STILL does something with mary and homer's children,
even though mary has been deleted */
});
So I either need a way to delete all references to descendant properties, or I need a way to indicate that the reference is tied to a deleted parent and ignore it in my calculations.
What is the proper/most maintainable way to accomplish this.
Unless you really need to keep all the children in one array for performance reasons, the easier solution is to scrap the all_children array in favor of a function that returns an array of all children, like
function getAllChildren () {
var all_children = [];
/* pseudocode:
foreach people(children) {
foreach children(child) {
all_children.push(child);
}
}
*/
return all_children;
}
This way, if you delete Mary or Homer, calling this function will automatically reflect the deletion.
If I understand the question correctly, you're afraid of the children objects still exist in memory after you remove parent object from the object array. This isn't the case for javascript, because the language doesn't implement true classes. And therefore each copy of the new ChildClass() is passed by value and not reference.
Therefore you don't need to keep track of the children references in order to clean them up.

get all children of specific parent in node js

I want to get all children of specific parent. I am new to node.js and not able to write recursive function to do this task
var roots = [1,2,6];
var documents = [
{
"parent_id":1
,childerens:[4,5]
}
,{
"parent_id":4
,childerens:[9]
}
,{
"parent_id":9
,childerens:[]
}
,{
"parent_id":5
,childerens:[3]
}
,{
"parent_id":3
,childerens:[]
}
]
roots.forEach(function (rootParentId) {
var allchilderens=getAllchild(rootParentId);
})
Here's an example of what I mean:
var allchilderens = getAllchild(1);
allchilderens == [4,5,9,3]
function findAllChildren(element,is_root,childerens) {
if(is_root==false&&element!=undefined){
childerens.push(element);
}
var doc = documents.find(o => o.parent_id === element);
if(doc["childerens"].length==0){
return [];
}
else{
doc["childerens"].forEach(function (element) {
findAllChildren(element,false,childerens);
})
}
}
var childerens=[];
console.log(findAllChildren(1,true,childerens));
console.log("childerens==>",childerens);
You really want to build a tree structure here as step one.
Each object should, instead of containing a list of children ids, it should contain the actual children. That way, when you want to get all children, you can traverse that tree.
You probably should consider revising your data structure here. Why not use an object, where the user id is the key (since it should be unique). Then you would just need to get those keys directly.
Having everything inside of an array is potentially bad, since you'd have to iterate through the array to find each child, which would have (at worst), a run time equal to the length of the array.

Pushing data into an array of objects

Alright, I've got this blank array of objects.
I am dynamically finding every node in a web page and each node is going to have it's own object and properties.
I need a way to throw the values I need into their respective objects property
So, for example, I find the body node. I now have a special little object for this node. I need to throw pretty much everything about this little guy into his object's properties.
So I pretty much need it to render like this:
Turning this:
<html>
<body style="margin:0; padding:0;" title="My Title">
<p>some text</p>
<div class="wrapper"></div>
<footer></footer>
</body>
</html>
Into this:
this.nodesInfo = [ // All nodes in the page's DOM
{
type: 'body', // ex: body, section, aside, div, etc.
id: 'myID', // the Id of that element
class: ['myClass1', 'myClass2'], // the class/class' of that element
depth: '2', // the level in the page's DOM in which that element sits, this will be an integer
parent: 'html', // that elements direct parent Node
children:['div.wrapper', 'p', 'footer'], // any child Nodes that, that element may be a parent to
text: '', // the text inside that element if any exists
attributes: ["style=margin:0; padding:0;", "title='My Title'"] // all attributes of this node
}
];
It would of course cycle through each node it discovered and do this for each node accordingly, until it ran out of nodes.
The class, children, and attributes properties are arrays for the simple possibility of multiples of any of these. Everything else is just a string since a node can't have more than one ID, title, or direct parent tag.
If a node does not contain some of these properties then that property would remain blank/null/undefined.
My question is simple. Is this possible, if not would I instead have to create each object individually and the push them into my nodesInfo array?
I think the easiest way to go about this would be making an object of each Node and then pushing them all (once they are all created) into an array.
I was building something like this the other night. This should work and you can add more stuff easily. http://jsfiddle.net/elclanrs/UHbMa/
$.fn.buildTree = function() {
var tree = {};
this.find('*').andSelf().each(function(i, v) {
var parents = $(this).parents().length - 1,
depth = 0;
while (depth < parents) {
depth++;
}
tree[v.tagName.toLowerCase() + '(' + i + ')'] = {
id: (v.id) ? '#' + v.id : '',
className: (v.className) ? '.' + v.className.replace(' ', '.') : '',
depth: depth
};
});
return tree;
};
// Then you can do this...
var tree = $('#element').buildTree();
for (var tag in tree) {
// Get your variables
var tag.match(/\w+/), // Get rid of `(n)`
id = tree[tag].id,
className = tree[tag].className,
depth = tree[tag].depth;
html = 'something';
// Bla bla
}

Categories