Javascript parent child Tree Merging - javascript

Below are the three arrays, I want to merge them using a mergelist function.
I am new to javascript, Please help me with this.
var list1 = [
{name: 'Parent'},
{name: 'child1', parent: ‘parent’},
{name: 'child2', parent: ‘parent’},
{name: 'child21', parent: 'child2'}
];
var list2 = [
{name: 'child1'},
{name: 'child11', parent: 'child1'},
{name: 'child12', parent: 'child1'}
];
var list3 = [
{name: 'child2'},
{name: 'child22', parent: 'child2'},
{name: 'child23', parent: 'child2'}
];
Implement a function that merges of all of the trees in the array into one combined tree.
Please help me with the code. Thank you
I have tried with this code but I only was able to do it for 2 trees and not exactly what I want.
var list1 = [
{
Parent: 'parent',
children: [
{
parent: 'child1',
children: []
},
{
parent: 'child2',
children: [
{
parent:'child21',
children :[]
}]
}]
}
];
var list2 = [
{
parent: 'child1',
children: [
{
parent: 'child11',
children: []
},
{
parent: 'child12',
children: []
}]
}
];
var list3 = [
{
parent: 'child2',
children: [
{
parent: 'child22',
children: []
},
{
parent: 'child23',
children: []
}]
}
]
var addNode = function(nodeId, array) {
array.push({parent: nodeId, children: []});
};
var placeNodeInTree = function(nodeId, parent, treeList) {
return treeList.some(function(currentNode){
// If currentNode has the same id as the node we want to insert, good! Required for root nodes.
if(currentNode.parent === nodeId) {
return true;
}
// Is currentNode the parent of the node we want to insert?
if(currentNode.parent === parent) {
// If the element does not exist as child of currentNode, create it
if(!currentNode.children.some(function(currentChild) {
return currentChild.parent === nodeId;
})) addNode(nodeId, currentNode.children);
return true;
} else {
// Continue looking further down the tree
return placeNodeInTree(nodeId, parent, currentNode.children);
}
});
};
var mergeInto = function(tree, mergeTarget, parentId) {
parentId = parentId || undefined;
tree.forEach(function(node) {
// If parent has not been found, placeNodeInTree() returns false --> insert as root element
if(!placeNodeInTree(node.parent, parentId, mergeTarget)){
list1.push({parent: node.parent, children:[]});
}
mergeInto(node.children, mergeTarget, node.parent);
});
};
mergeInto(list2, list1);
document.write('<pre>');
document.write(JSON.stringify(list1, null, 4));
console.log(list1);
document.write('</pre>');

It looks like an easy problem, but the data source is misleading with duplicate items sometimes with parent property and sometimes without.
The first part is to clean the data and build a flat array with single name and if possible with parent. The data is stored in data. The first output is this result.
The second part is to build a tree with the data. For every object, a node for name is build and for every given parent a node is created, if it is not already there.
var list1 = [{ name: 'parent' }, { name: 'child1', parent: 'parent' }, { name: 'child2', parent: 'parent' }, { name: 'child21', parent: 'child2' }],
list2 = [{ name: 'child1' }, { name: 'child11', parent: 'child1' }, { name: 'child12', parent: 'child1' }],
list3 = [{ name: 'child2' }, { name: 'child22', parent: 'child2' }, { name: 'child23', parent: 'child2' }],
data = function (data) {
var o = {}, r = [];
data.forEach(function (a) {
if (!o[a.name]) {
r.push(a);
o[a.name] = a;
return;
}
if (!(parent in o[a.name])) {
o[a.name] = a;
}
});
return r;
}([].concat(list1, list2, list3)),
tree = function (data) {
var r, o = {};
document.write('<pre>data: ' + JSON.stringify(data, 0, 4) + '</pre>');
data.forEach(function (a, i) {
a.children = o[a.name] && o[a.name].children;
o[a.name] = a;
if (a.parent === undefined) {
r = a;
return;
}
o[a.parent] = o[a.parent] || {};
o[a.parent].children = o[a.parent].children || [];
o[a.parent].children.push(a);
});
return r;
}(data);
document.write('<pre>tree: ' + JSON.stringify(tree, 0, 4) + '</pre>');

Related

Javascript access deep child object by array of index numbers and assign new value

const newFolders = [{name:'new1'},{name:'new2'}]
let folders = [
{
name: 'root',
children:[
{name:'folder1'},
{
name:'folder2',
children:[
{name: 'folder2.1'},
{
name: 'folder2.2',
children: [
{name:'target folder'}
]
},
{name: 'folder2.3'},
]
},
{name:'folder3'}
]
}
]
// I don't want to use the below line to assign the newFolders variable to the target object
folders[0].children[1].children[1].children[0].children = newFolders;
// instead I have below
const child_index_map = [0,1,1,0]; // it should be any length for future access
// what I tried is (unsuccessful)
let temp = folders;
child_index_map.forEach((i, index)=>{
temp = temp[i].children;
if(index === child_index_map.length - 1){
temp = newFolders;
}
});
let folders = temp;
This is my folder structure and it should be any number of childrens.
I want to access the {name:'target folder'} object and assign newFolders to the object, but not the typical way like folders[0].children[1].children[1].children[0].children = newFolders;
instead of the above, I have index array const child_index_map = [0,1,1,0]
is there any possible way
Expected result
let folders = [
{
name: 'root',
children:[
{name:'folder1'},
{
name:'folder2',
children:[
{name: 'folder2.1'},
{
name: 'folder2.2',
children: [
{
name:'target folder',
children: [{name:'new1'},{name:'new2'}] // <--- this
}
]
},
{name: 'folder2.3'},
]
},
{name:'folder3'}
]
}
]
There were some issues with the code in array.forEach area.
const newFolders = [{ name: 'new1' }, { name: 'new2' }]
let folders = [
{
name: 'root',
children: [
{ name: 'folder1' },
{
name: 'folder2',
children: [
{ name: 'folder2.1' },
{
name: 'folder2.2',
children: [
{ name: 'target folder' }
]
},
{ name: 'folder2.3' },
]
},
{ name: 'folder3' }
]
}
]
// I don't want to use the below line to assign the newFolders variable to the target object
// folders[0].children[1].children[1].children[0].children = newFolders;
// instead I have below
const child_index_map = [0, 1, 1, 0]; // it should be any length for future access
// what I tried is (unsuccessful) => Is successful now
let temp = folders;
child_index_map.forEach((i, index) => {
temp = temp[i];
if (index === child_index_map.length - 1) {
temp.children = newFolders;
}
if (temp.children) {
temp = temp.children;
}
});
// There is no need to update your floders variable.
// folders = temp;
console.log(folders);
You can use a recursive function to find the folder and then modify it.
const newFolders = [{name:'new1'},{name:'new2'}]
let folders = [
{
name: 'root',
children:[
{name:'folder1'},
{
name:'folder2',
children:[
{name: 'folder2.1'},
{
name: 'folder2.2',
children: [
{name:'target folder'}
]
},
{name: 'folder2.3'},
]
},
{name:'folder3'}
]
}
]
const child_index_map = [0,1,1,0];
const getParentFolder = (folders, childMap, currentIndex) => {
if(currentIndex === childMap.length - 1) return folders;
if(Array.isArray(folders)) return getParentFolder(folders[childMap[currentIndex]], childMap, currentIndex+1);
return getParentFolder(folders.children[childMap[currentIndex]], childMap, currentIndex+1);
}
const parentFolder = getParentFolder(folders, child_index_map, 0);
parentFolder.children = newFolders;
console.log(folders)
You can change the attachFolderName variable what you want.
let folders = [
{
name: 'root',
children:[
{name:'folder1'},
{
name:'folder2',
children:[
{name: 'folder2.1'},
{
name: 'folder2.2',
children: [
{
name:'target folder',
children: [{name:'new1'},{name:'new2'}] // <--- this
}
]
},
{name: 'folder2.3'},
]
},
{name:'folder3'}
]
}
]
let findAndAdd = (data, obj, search) => {
function recursiveSearch (child) {
if (child.children) {
let filtered = child.children.find((f) => {
return f.name === search
})
if (filtered) {
if (!filtered.children)
filtered.children = [obj]
else
filtered.children.push(obj)
return;
}
child.children.forEach((c) => {
recursiveSearch(c)
})
}
}
recursiveSearch(data[0])
}
let attachFolderName = "target folder"
findAndAdd(folders, {name: "new test with recursive"}, attachFolderName)
console.log(folders)

Building a tree recursively in JavaScript

I am trying to build a tree recursively from an array of objects. I am currently using the reduce() method to iterate through the items in the array and figure out which children belong to a particular item and populating it, then recursively populating the children of those children and so on. However, I have been unable to take the last nodes(e.g persian and siamese in this case) and put them in array(see expected and current output below)
let categories = [
{ id: 'animals', parent: null },
{ id: 'mammals', parent: 'animals' },
{ id: 'cats', parent: 'mammals' },
{ id: 'dogs', parent: 'mammals' },
{ id: 'chihuahua', parent: 'dogs' },
{ id: 'labrador', parent: 'dogs' },
{ id: 'persian', parent: 'cats' },
{ id: 'siamese', parent: 'cats' }
];
const reduceTree = (categories, parent = null) =>
categories.reduce(
(tree, currentItem) => {
if(currentItem.parent == parent){
tree[currentItem.id] = reduceTree(categories, currentItem.id);
}
return tree;
},
{}
)
console.log(JSON.stringify(reduceTree(categories), null, 1));
expected output:
{
"animals": {
"mammals": {
"cats": [ // <-- an array of cat strings
"persian",
"siamese"
],
"dogs": [ // <-- an array of dog strings
"chihuahua",
"labrador"
]
}
}
}
current output:
{
"animals": {
"mammals": {
"cats": { // <-- an object with cat keys
"persian": {},
"siamese": {}
},
"dogs": { // <-- an object with dog keys
"chihuahua": {},
"labrador": {}
}
}
}
}
How should I go about solving the problem?
I put a condition to merge the result as an array if a node has no child. Try this
let categories = [
{ id: 'animals', parent: null },
{ id: 'mammals', parent: 'animals' },
{ id: 'cats', parent: 'mammals' },
{ id: 'dogs', parent: 'mammals' },
{ id: 'chihuahua', parent: 'dogs' },
{ id: 'labrador', parent: 'dogs' },
{ id: 'persian', parent: 'cats' },
{ id: 'siamese', parent: 'cats' }
];
const reduceTree = (categories, parent = null) =>
categories.reduce(
(tree, currentItem) => {
if(currentItem.parent == parent){
let val = reduceTree(categories, currentItem.id);
if( Object.keys(val).length == 0){
Object.keys(tree).length == 0 ? tree = [currentItem.id] : tree.push(currentItem.id);
}
else{
tree[currentItem.id] = val;
}
}
return tree;
},
{}
)
console.log(JSON.stringify(reduceTree(categories), null, 1));
NOTE: if your data structure changes again this parser might fail for some other scenarios.
Here is a solution without recursion:
const categories = [{ id: 'animals', parent: null },{ id: 'mammals', parent: 'animals' },{ id: 'cats', parent: 'mammals' },{ id: 'dogs', parent: 'mammals' },{ id: 'chihuahua', parent: 'dogs' },{ id: 'labrador', parent: 'dogs' },{ id: 'persian', parent: 'cats' },{ id: 'siamese', parent: 'cats' }];
// Create properties for the parents (their values are empty objects)
let res = Object.fromEntries(categories.map(({parent}) => [parent, {}]));
// Overwrite the properties for the parents of leaves to become arrays
categories.forEach(({id, parent}) => res[id] || (res[parent] = []));
// assign children to parent property, either as property of parent object or as array entry in it
categories.forEach(({id, parent}) => res[parent][res[id] ? id : res[parent].length] = res[id] || id);
// Extract the tree for the null-entry:
res = res.null;
console.log(res);

How to build the path to each node in a tree recursively - JavaScript?

My data structure will look like this:
var tree = [
{
id: 1,
children: []
}, {
id: 2,
children: [
{
id: 3,
children: []
}
]
}
];
There can be any number of nodes or children on one branch.
My goal is to build a path to every node.
For example id: 3 will have a path of 1 > 2 > 3
id: 2 will have a path of 1 > 2
I want to run my tree through the algorithm so it will be modified like this:
var tree = [
{
id: 1,
path: [1],
children: []
}, {
id: 2,
path: [2],
children: [
{
id: 3,
path: [2, 3],
children: []
}
]
}
];
I have written an algorithm that will visit all of the nodes in the tree:
https://plnkr.co/edit/CF1VNofzpafhd1MOMVfj
How can I build the path to each node?
Here is my attempt:
function traverse(branch, parent) {
for (var i = 0; i < branch.length; i++) {
branch[i].visited = true;
if (branch[i].path === undefined) {
branch[i].path = [];
}
if (parent != null) {
branch[i].path.push(parent);
}
if (branch[i].children.length > 0) {
traverse(branch[i].children, branch[i].id);
}
}
}
Beside the unclear taking of not directly involved parents, you could store the path as arrray and take it for each nested iteration.
function iter(path) {
path = path || [];
return function (o) {
o.path = path.concat(o.id);
if (o.children) {
o.children.forEach(iter(o.path));
}
}
}
var tree = [{ id: 1, children: [] }, { id: 2, children: [{ id: 3, children: [] }] }];
tree.forEach(iter());
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You made a mistake
Your root node is an array, but all other nodes are objects.
This makes your program inconsistent and needlessly complex to handle the root node difference – the solution is to stop writing data using literals – you're bound to make mistakes like you did above
Instead, just make some simple data constructors and your complexities vanish into thin air
const Node = (id, ...children) =>
({ id, children })
const PathNode = (id, path, ...children) =>
({ id, path, children })
const addPaths = ({id, children}, acc = []) =>
PathNode (id, acc, children.map (child =>
addPaths (child, [...acc, id])))
const tree =
Node (0, Node (1),
Node (2, Node (3)))
console.log (tree)
// { id: 0, children: [
// { id: 1, children: [ ] },
// { id: 2, children: [
// { id: 3, children: [ ] } ] } ] }
console.log (addPaths (tree))
// { id: 0, path: [ ], children: [
// { id: 1, path: [ 0 ], children: [ ] },
// { id: 2, path: [ 0 ], children: [
// { id: 3, path: [ 0, 2 ], children: [ ] } ] } ] }
You could use reduce method to create a recursive function and pass the previous path values in recursive calls as an array of id's.
var tree = [{ id: 1, children: [] }, { id: 2, children: [{ id: 3, children: [] }] }];
function getPaths(data, prev = []) {
return data.reduce((r, { id, children }) => {
const o = { id, children, path: [...prev, id] }
if (children) {
o.children = getPaths(children, o.path)
}
r.push(o)
return r
}, [])
}
console.log(getPaths(tree))

How do I find the path to a deeply nested unique key in an object and assign child objects accordingly?

With a given flat list:
let list = [
{
key: 1,
parent: null,
},
{
key: 2,
parent: 1,
},
{
key: 3,
parent: null,
},
{
key: 4,
parent: 1,
},
{
key: 5,
parent: 2,
}
]
How do I create a nested object like the one below?
let nest = {
children: [
{
key: 1,
children: [
{
key: 2,
children: [
{
key: 5,
children: []
}
]
},
{
key: 4,
children: []
}
]
},
{
key: 3,
children: []
}
]
}
I'm not sure how to approach this. The solution I have in mind would have to iterate over the list over and over again, to check if the object's parent is either null, in which case it gets assigned as a top-level object, or the object's parent already exists, in which case we get the path to the parent, and assign the child to that parent.
P.S.
I don't think this is a duplicate of any of the below
this checks for a key in a flat object.
this doesn't show anything that would return a path, given a unique key.
For building a tree, you could use a single loop approach, by using not only the given key for building a node, but using parent as well for building a node, where the dendency is obviously.
It uses an object where all keys are usesd as reference, like
{
1: {
key: 1,
children: [
{
/**id:4**/
key: 2,
children: [
{
/**id:6**/
key: 5,
children: []
}
]
},
{
/**id:8**/
key: 4,
children: []
}
]
},
2: /**ref:4**/,
3: {
key: 3,
children: []
},
4: /**ref:8**/,
5: /**ref:6**/
}
The main advantage beside the single loop is, it works with unsorted data, because of the structure to use keys and parent information together.
var list = [{ key: 1, parent: null, }, { key: 2, parent: 1, }, { key: 3, parent: null, }, { key: 4, parent: 1, }, { key: 5, parent: 2, }],
tree = function (data, root) {
var r = [], o = {};
data.forEach(function (a) {
var temp = { key: a.key };
temp.children = o[a.key] && o[a.key].children || [];
o[a.key] = temp;
if (a.parent === root) {
r.push(temp);
} else {
o[a.parent] = o[a.parent] || {};
o[a.parent].children = o[a.parent].children || [];
o[a.parent].children.push(temp);
}
});
return r;
}(list, null),
nest = { children: tree };
console.log(nest);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Search deep in array and delete

I got the following array:
var arr = [
{
1: {
id: 1,
title: 'test'
},
children: [
{
1: {
id: 2,
title: 'test2'
}
}
]
}
];
The objects directly in the array are the groups. The 1: is the first language, 2: is second etc. The id is stored in every language object (due to the database I'm using). The children array is built the same way as the 'arr' array.
Example of multiple children:
var arr = [
{
1: {
id: 1,
title: 'test'
},
children: [
{
1: {
id: 2,
title: 'test2'
},
children: [
{
1: {
id: 3,
title: 'test3',
},
children: []
}
]
}
]
}
];
Now I need to delete items from this array. You can have unlimited children (I mean, children can have children who can have children etc.). I have a function which needs an ID parameter sent. My idea is to get the right object where the ID of language 1 is the id parameter. I got this:
function deleteFromArray(id)
{
var recursiveFunction = function (array)
{
for (var i = 0; i < array.length; i++)
{
var item = array[i];
if (item && Number(item[1].ID) === id)
{
delete item;
}
else if (item && Number(item[1].ID) !== id)
{
recursiveFunction(item.children);
}
}
};
recursiveFunction(arr);
}
However, I'm deleting the local variable item except for the item in the array. I don't know how I would fix this problem. I've been looking all over the internet but haven't found anything.
This proposal features a function for recursive call and Array.prototype.some() for the iteration and short circuit if the id is found. Then the array is with Array.prototype.splice() spliced.
var arr = [{ 1: { id: 1, title: 'test' }, children: [{ 1: { id: 2, title: 'test2' }, children: [{ 1: { id: 3, title: 'test3', }, children: [] }] }] }];
function splice(array, id) {
return array.some(function (a, i) {
if (a['1'].id === id) {
array.splice(i, 1)
return true;
}
if (Array.isArray(a.children)) {
return splice(a.children, id);
}
});
}
splice(arr, 2);
document.write('<pre>' + JSON.stringify(arr, 0, 4) + '</pre>');
var arr = [{ 1: { id: 1, title: 'test' }, children: [{ 1: { id: 2, title: 'test2' }, children: [{ 1: { id: 3, title: 'test3', }, children: [] }] }] }];
function deleteFromArray(id) {
function recursiveFunction(arr) {
for (var i = 0; i < arr.length; i++) {
var item = arr[i];
if (item && Number(item[1].id) === id) {
arr.splice(i, 1);
} else if (item && Number(item[1].id) !== id) {
item.children && recursiveFunction(item.children);
}
}
};
recursiveFunction(arr);
};
deleteFromArray(2);
document.getElementById("output").innerHTML = JSON.stringify(arr, 0, 4);
<pre id="output"></pre>
jsfiddle: https://jsfiddle.net/x7mv5h4j/2/
deleteFromArray(2) will make children empty and deleteFromArray(1) will make arr empty itself.

Categories

Resources