Join tree paths for each node - javascript

This should be easy, but I am having a brain fart, I have a structure like this:
interface EntitiesMap {
name: string,
children: Array<EntitiesMap>,
}
The tree has no cycles. So that might look like:
const m = {
name: 'foo',
children: [{
name: 'Baz1',
children: [ ...etc ]
}, {
name:'Bar',
children:[]
}]
}
For each node in the tree, I am simply looking to join the name fields.
So for example if I had this tree:
foo ----- Baz1 ---- Baz2 ------Baz5
\ \ \
Bar \ \
Baz3 Baz4
So for the foo (root) node, I would have these list of names:
FooBar
FooBaz1Baz2Baz5
FooBaz1Baz3
FooBaz1Baz2Baz4
Since we have 4 tips of the tree, then we have 4 paths for the root (foo) node.
For the Baz1 node, we would have 3 tips:
Baz1Baz2Baz5
Baz1Baz2Baz4
Baz1Baz3
There must be a simple way of generating these strings, based off of a the EntitiesMap tree structure, but I am really struggling with it. I am looking for a good algorithm/simple that's not too clever.

You can do this recursively - get names for each child and concatenate "this" node's name with every result for each child:
function joinNames(node) {
if (node.children.length === 0) return [node.name];
let names = [];
node.children.forEach(n =>
joinNames(n).forEach(cn =>
names.push(node.name + cn)));
return names;
}
const m = {
name: 'Foo',
children: [{
name: 'Baz1',
children: [{
name: 'Baz3',
children: []
},
{
name: 'Baz2',
children: [{
name: 'Baz4',
children: []
},
{
name: 'Baz5',
children: []
}
]
}
]
},
{
name: 'Bar',
children: []
}
]
};
console.log(joinNames(m));
console.log(joinNames(m.children[0])); // 'Baz1' as root

Sometimes passing the current path as an argument to a recursive function can simplify the function and make things a little easier to understand. Here's a possible way to do that:
const m = {name: 'foo',children: [{name: 'Baz1',children: [ {name: "Baz3",children: []},{name: "Baz2",children: [{name: "Baz5",children: []},{name: "Baz4",children: []}]}]}, {name:'Bar',children:[]}]}
function getPath(node, path=""){
return (node.children.length == 0)
? [path + node.name]
: node.children.reduce((arr, child) =>
arr.concat(...getPath(child, path + node.name)), [])
}
console.log(getPath(m))

Related

How to traverse through a tree like nested data structure of unknown depth in order to find and collect addressable array items?

Say I have an array that looks as such:
[{
"name": "Audiograms",
"folders": [{
"name": "2022"
}, {
"name": "2021"
}, {
"name": "2020"
}]
}, {
"name": "Patient Paperwork"
}, {
"name": "Repairs"
}]
And this array can have an infinite amount of objects and sub-objects, similar to a file tree.
I have an array letting me know the name of the folders I need to access from the root of the object, like:
["Audiograms", "2022"]
I also do not know this value ahead of time, nor do I know how many items are in this array ahead of time.
How would I be able to actually traverse this file tree using the array of names? I wish to do things like maybe pop the matching object out and move it to another part of the file tree.
Thank you!
OP
"I wish to do things like maybe pop the matching object out and move it to another part of the file tree."
In order to achieve follow-up tasks like the above mentioned one, the next provided solution walks the OP's folder structure and collects for each addressable match an object of two references, target and parent, where the former is the reference of the to be found folder-item, and the latter is the reference of its parent folder-item.
The solution got achieved by a recursively implemented reducer function.
function collectAddressableFolderRecursively(collector, folderItem) {
const { name = null, folders = [] } = folderItem;
const {
address: [parentName, childName], result,
} = collector;
if (name === parentName && folders.length) {
const targetFolder = folders
.find(({ name }) => name === childName) ?? null;
if (targetFolder !== null) {
result.push({
target: targetFolder,
parent: folderItem,
});
}
}
result.push(
...folders.reduce(collectAddressableFolderRecursively, {
address: [parentName, childName],
result: [],
})
.result
);
return collector;
}
const folders = [{
name: 'Audiograms',
folders: [{
name: '2022',
folders: [{
name: 'Audiograms',
folders: [{
name: '2022',
}, {
name: 'foo',
}],
}],
}, {
name: '2021',
}, {
name: '2020',
}]
}, {
name: 'Patient Paperwork',
}, {
name: 'Repairs',
folders: [{
name: 'Audiograms',
folders: [{
name: '2022',
}, {
name: 'bar',
}],
}, {
name: 'baz',
}],
}]
const address = ['Audiograms', '2022'];
const { result } = folders
.reduce(collectAddressableFolderRecursively, {
address,
result: [],
});
console.log({ address, result });
.as-console-wrapper { min-height: 100%!important; top: 0; }

Object hierarchy from XML document using JavaScript

Given an XML document such as this:
<Root>
<Child1>
<ChildOfChild1_1>
<FinalChild> </FinalChild>
</ChildOfChild1_1>
<ChildOfChild1_2> </ChildOfChild1_2>
</Child1>
<Child2>
<ChildOfChild2_1> </ChildOfChild2_1>
</Child2>
<Child3> </Child3>
</Root>
I would like to return a object which has only two parameters, name and children. It would look like:
{
name: "Root"
children: [
{
name: "Child1"
children: [
{
name: "ChildOfChild1_1"
children:[
{
name: "FinalChild"
children: null
}
]
},
{
name: "ChildOfChild1_2"
children: null
}
]
},
{
name: "Child2"
children: [
{
name: "ChildOfChild2_1"
children: null
}
]
},
{
name: "Child3"
children: null
},
]
}
I do not care about node attributes, only nodeName and if they have children. I wrote code to get the first level of children but cannot wrap my head around the recursive part to get as deep as necessary. Thanks.
Below is what I have so far:
//assuming block is the root node and the root will at least have 1 child
let root = {
name = '',
children = []
};
root.name = block.nodeName;
for(let i=0 ; i<block.children.length ; i++) {
root.children.push(getChildren(block.children[i]));
}
function getChildren(data) {
let child = {};
child.name = data.nodeName;
child.children = []; //stuck here
return child;
}
The task is much simpler than you think.
You want a function that returns a node name and the list of node children, each of them processed in the exact same way:
function toObject(node) {
return {
name: node.nodeName,
children: [...node.children].map(toObject)
};
}
That's it.
const xmlDoc = new DOMParser().parseFromString(`<Root>
<Child1>
<ChildOfChild1_1>
<FinalChild> </FinalChild>
</ChildOfChild1_1>
<ChildOfChild1_2> </ChildOfChild1_2>
</Child1>
<Child2>
<ChildOfChild2_1> </ChildOfChild2_1>
</Child2>
<Child3> </Child3>
</Root>
`, "application/xml");
function toObject(node) {
return {
name: node.nodeName,
children: [...node.children].map(toObject)
};
}
const tree = toObject(xmlDoc.documentElement);
console.log(tree);
If you really want null when there are no children (an empty array is much easier to handle down the line, trust me), I'm sure you can make the necessary change in toObject yourself.

How to update a JavaScript object with nested array and objects under n levels?

Following the below tree
let dumbTree = {
name: "vob",
uuid: "edbf146d-c9ee-4568-8c1e-a095f8ad4aff",
children: [
{
name: "A0A",
uuid: "8655460b-4862-4d11-800a-870482f4701b",
nodeType: "project",
children: []
},
{
name: "A1A",
uuid: "8655460b-4862-4d11-800a-870482f4701b",
nodeType: "project",
children: []
}
]
}
I'd like to convert incrementally path into the same kind of structure.
In that example, I've got a path /vob/A0A, and /vob/A1A, and then if I'm clicking on the leaf A1A, it'd give me an array like from an API call:
['/vob/A1A/A1A111', '/vob/A1A/A1A112', '/vob/A1A/A1A113'], so then I'd like to populate the tree accordingly
dumbTree = {
name: "vob",
uuid: "edbf146d-c9ee-4568-8c1e-a095f8ad4aff",
children: [
{
name: "A0A",
uuid: "8655460b-4862-4d11-800a-870482f4701b",
nodeType: "project",
children: []
},
{
name: "A1A",
uuid: "8655460b-4862-4d11-800a-870482f4701c",
nodeType: "project",
children: [
{
name: 'A1A111',
children: []
},
{
name: 'A1A112',
children: []
}
...
]
}
]
}
And then clicking on A1AAAA would give other children that will replace the empty array in A1A sub object, etc...
Is there any efficient way to do it?
I tried something with multiple finds on nested children, but this approach isn't going to be very effective on n levels.
const testChildren2 = [
{
name: "AAA123",
uuid: "00214ef8-fb29-4326-b236-3c90ab47eaaf",
children: []
}
]
let dumbTreeIndexA = dumbTree.children.findIndex((elt) => elt.name === 'AAA')
dumbTree.children[dumbTreeIndexA].children = testChildren2
// vobs/AAA/AAA123
const studyIndex = dumbTree.children[dumbTreeIndexA].children.findIndex((elt) => elt.name === 'AAA123')
I'm open to existing libraries such as lodash, ramdaJS, but didn't find a good way to do this yet.

Filter an array of objects with a second array with multiple values

I am trying to write a function to take the first object in the "parent" array, pull out the child field (which is in that array) and use that field to filter the second object called "child".
I want to get all the related records from the child object that are in the child field in the parent object.
Expected output
child: [
{
**id: 1,**
name: 'Jimmy Yukka',
},
{
**id: 2,**
name: 'Up North',
}
INPUT
Parent: [
{
**id: 1,**
name: 'Melbourne Bands',
**child: [1, 2]**
}
I have the following data
Parent: [
{
**id: 1,**
name: 'Melbourne Bands',
**child: [1, 2]**
},
{
id: 2,
name: 'Sydney Bands',
child: [3]
}
],
child: [
{
**id: 1,**
name: 'Jimmy Yukka',
},
{
**id: 2,**
name: 'Up North',
},
{
id: 3,
url: 'jimmyyukka.com',
name: 'INXS',
CreatedByUserId: 1
}
],
The code of the function I have implemented so far:
currentChildrenIds(ParentId, parentData, childData) {
const singleParentRecord = parentData.filter(function(parent) {
return parent.id === ParentId;
});
const parentsChildIds = singleParentRecord[0].books;
const childRecords = childData.filter(function(child) {
return child.id === parentsChildIds
});
return childRecords
}
NOTES
This bit here is where it is wrong
const childRecords = childData.filter(function(child) {
return child.id === parentsChildIds
This bit here is also a bit rubbish (hardcoding the [0])but not I'm not sure how I should be coding it correctly
const parentsChildIds = singleParentRecord[0].books;
here,
const childRecords = childData.filter(function(child) {
return child.id === parentsChildIds
parentsChildIds is a reference to an array: you don't want to test if an id is === to a a reference,
You have to be explicit and check if the id is contained in the array:
const childRecords = childData.filter(function(child) {
return parentsChildIds.includes(child.id)
Regarding the singleParentRecord[0] that does feel weird,
since you know the method filter will always return an array of size 1 or 0,
you can use the method find instead of filter
Also in functionnal programming (array functions such as filter, map, find...)
I advice you to read a bit about the arrow function syntax because:
The syntex is more dense and it makes it easier for your brain to understand when several functions are chained
If you want to use variables which are defined outside of the function it will be available only inside of an arrow function
your code with an arrow function:
const childRecords = childData.filter((child) => {
return child.id === parentsChildIds
}
Try this:
const Parent = [
{
id: 1,
name: 'Melbourne Bands',
child: [1, 2]
},
{
id: 2,
name: 'Sydney Bands',
child: [3]
}
];
const children = [
{
id: 1,
name: 'Jimmy Yukka',
},
{
id: 2,
name: 'Up North',
},
{
id: 3,
url: 'jimmyyukka.com',
name: 'INXS',
CreatedByUserId: 1
}
];
// We create a new array with Array.map
const result = Parent.map(parent => ({
// Spread properties of the parent
...parent,
// Override the child property and filter the children array with the `includes` method
child: children.filter(child => parent.child.includes(child.id)),
}))
console.log(result);

Identify circular dependency in a Json object and remove all element after 2 depth

I have a json object something like this:
var temp1 = {
name: "AMC",
children: [
{
name: "cde",
children: [
{
name: "AMC",
children: [
{
name: "cde",
children: [
{
name: "AMC",
children: [
//.............. continues as curcular depndency
]
}
]
}
]
}
]
},
{
name: "mnp",
children: [
{
name: "xyz",
children: []
}
]
}
]
}
Due to this cicular dependency, JSON.stringify is failing.
I have done enough google and searching to get the solution for this but could not find much help.
So here basically I want to detect a circular dependency in the json object and add a new key to the object, saying cricular: true and remove all the subsequent node.
So here is the result output what I am looking :
var temp1 = {
name: "AMC",
children: [
{
name: "cde",
circular: true,
children: [ // No children here as it is curcular dependency
]
},
{
name: "mnp",
children: [
{
name: "xyz",
children: []
}
]
}
]
}
There is a way, which I think can solve it, where I can loop through all the children unless there is no children upto maximum 2 levels, but that way I will miss valid children which are having depth more than 3.
I hope my question is clear. If not please let me know I will try to expand this further.
A recursive function solves this:
function check(stack,parent, obj){
stack = stack || []; //stack contains a list of all previously occurred names
var found = stack.find(function(parent){
return (parent==obj.name && obj.children.length>0); //checks to see if the current object name matches any in the stack.
});
if(!found && obj.children.length>0){
stack.push(obj.name); //adds the current object name to the list.
obj.children.forEach(function(child){
check(stack,obj, child);//recursively checks for all children.
})
}
else if(found){
parent.children=[];
parent.circular=true;
stack.pop(obj.name);
return;
}
else{
return;
}
}
check([],temp1, temp1)
This leads to alteration of the original object passed.
Hope this helps!
use console.table(circularObj) to help you in debugging

Categories

Resources