I've come across this problem before, say with Categories or Tags.
You have multiple tags that can be children of each other:
{ id: 1, name: 'Sports', parent_id: null }
{ id: 2, name: 'Fruits', parent_id: null }
{ id: 3, name: 'Citrus', parent_id: 2 }
{ id: 4, name: 'Orange', parent_id: 3 }
{ id: 5, name: 'Hockey', parent_id: 1 }
Another representation of these nodes:
Sports -> Hockey
Fruits -> Citrius -> Orange
What is the algorithm that efficiently finds the top-most parent for each node? So we can go from Orange -> Fruits in O(1) time.
(Requires some preprocessing).
As another answer suggests, you could first create a dictionary keyed by node id. But then when finding the top-level parent of a node, that information should bubble up from recursion and be used to update all intermediate nodes, since they have the same top-level parent. This will save on the work to do and make the whole proces O(n).
const categories = [
{ id: 1, name: 'Sports', parent_id: null },
{ id: 2, name: 'Fruits', parent_id: null },
{ id: 3, name: 'Citrus', parent_id: 2 },
{ id: 4, name: 'Orange', parent_id: 3 },
{ id: 5, name: 'Hockey', parent_id: 1 },
{ id: 6, name: 'Apple', parent_id: 2 }
];
// Build a Map of nodes keyed by node id:
const map = new Map(categories.map(node => [node.id, node]));
// Enrich each node with parent and top node-references
function extendNode(node) {
if (!node.top) {
node.parent = map.get(node.parent_id);
node.top = node.parent ? extendNode(node.parent) : node;
}
return node.top;
}
categories.forEach(extendNode);
// Example use:
const orange = map.get(4);
console.log(orange.name, "=>", orange.top.name);
.as-console-wrapper { max-height: 100% !important }
Idea:
First create a dictionary for each id that takes O(n) where n is the length of categories array.
Traverse each array element and look for its top parent so it takes O(n*d) where d is the depth of its parent and build a dictionary that contains keys as child names and values as parent properties.
So this approach takes O(n*d) for preprocessing.
But if you use DP for getParent() the time complexity would be O(n*α) where α is a slowly growing function. so it's not exactly O(n) but pretty close to the linear runtime.
Idea of DP in details:
We can also reduce the computations of getParent() by using Dynamic programming. For example, look at this dependency path Fruits -> Citrius -> Orange so when we visit Citrius we know that ends up in Fruits so we need to memoize the result. so when next time when we start from Orange the next node is Citrius and we know from Citrius we can have the result Fruits. So no need to visit all nodes.
const categories = [
{ id: 1, name: 'Sports', parent_id: null },
{ id: 2, name: 'Fruits', parent_id: null },
{ id: 3, name: 'Citrus', parent_id: 2 },
{ id: 4, name: 'Orange', parent_id: 3 },
{ id: 5, name: 'Hockey', parent_id: 1 },
{ id: 6, name: 'Apple', parent_id: 2 }
];
//Build a dictionary for each id so we can look up its parent efficiently while traversing
const refDict = categories.reduce((acc, item) => ({ ...acc,
[item.id]: item
}), {});
//it looks for its parent
const getParent = (id) => {
const child = refDict[id];
if (child.parent_id === null) return { ...child
};
return getParent(child.parent_id);
}
//final dict which contains keys as the name of every child and value as its parent's properties
//So we can look at any topmost parent in constant time
const finalDict = categories.reduce((acc, item) => (item.parent_id === null ? { ...acc
} : { ...acc,
[item.name]: getParent(item.id)
}), {});
console.log(finalDict);
console.log("parent - ", finalDict["Orange"]);
.as-console-wrapper { max-height: 100% !important }
Hope it helps!
Related
I want to create a tree from the data provided using recursion. I am also trying to get the tree to pass an npm test, but when I run the test, it is failing. I am getting a tree, but it looks different than the one it is supposed to look like.
Here is the code (with instructions in a comment):
let data = [
{ id: 'animals', parent: null },
{ id: 'mammals', parent: 'animals' },
{ id: 'cats', parent: 'mammals' },
{ id: 'dogs', parent: 'mammals' },
{ id: 'labrador', parent: 'dogs' },
{ id: 'retreiver', parent: 'dogs' },
{ id: 'corgi', parent: 'dogs' },
{ id: 'persian', parent: 'cats' },
{ id: 'siamese', parent: 'cats' },
{ id: 'maineCoon', parent: 'cats' }
];
// write a function: makeTree(obj)
// that takes a flat data stucture,
// as seen above, and return
// a tree structure as seen below.
// Must use recursion.
function makeTree(arr, parent) {
return arr
.filter((data) => data.parent === parent)
.reduce(
(tree, data) => [
...tree,
{
...data,
child: makeTree(arr, data.id),
},
],
[],
)
}
console.log('making tree')
console.log(
JSON.stringify(
makeTree(data, null)
, null, 2
)
)
// the tree should look like this when done
let reutrn = {
animals: {
mammals: {
dogs: {
labrador: {},
retreiver: {},
corgi: {},
},
cats: {
persian: {},
siamese: {},
maineCoon: {}
}
}
}
}
Your reduce should produce a plain object, not an array -- there is no array in your desired output. Also, your code produces a property child, but there is no such property in your desired output. It seems like code that is specifically intended for a different output structure.
Here is the adapted reduce call:
function makeTree(arr, parent) {
return arr
.filter((data) => data.parent === parent)
.reduce(
(tree, {id}) => ({
...tree,
[id]: makeTree(arr, id),
}),
{},
);
}
const data = [{ id: 'animals', parent: null },{ id: 'mammals', parent: 'animals' },{ id: 'cats', parent: 'mammals' },{ id: 'dogs', parent: 'mammals' },{ id: 'labrador', parent: 'dogs' },{ id: 'retreiver', parent: 'dogs' },{ id: 'corgi', parent: 'dogs' },{ id: 'persian', parent: 'cats' },{ id: 'siamese', parent: 'cats' },{ id: 'maineCoon', parent: 'cats' }];
console.log(makeTree(data, null));
It should be noted that this is not an efficient way of doing it. It needs several passes of the whole array, giving this a quadratic time complexity, while an iterative method can do this with a linear time complexity.
Trincot gave you a way to fix the code you've been given.
But there is a simpler way to do this recursively, using the relatively new, but widely supported Object .fromEntries. With this, we get quite simple code:
const makeTree = (xs, root = null) => Object .fromEntries (
xs .filter (({parent}) => parent == root)
.map (({id}) => [id, makeTree (xs, id)])
)
const data = [{id: 'animals', parent: null}, {id: 'mammals', parent: 'animals'}, {id: 'cats', parent: 'mammals'}, {id: 'dogs', parent: 'mammals'}, {id: 'labrador', parent: 'dogs'}, {id: 'retreiver', parent: 'dogs'}, {id: 'corgi', parent: 'dogs'}, {id: 'persian', parent: 'cats'}, {id: 'siamese', parent: 'cats'}, {id: 'maineCoon', parent: 'cats'}]
console .log (makeTree (data))
.as-console-wrapper {max-height: 100% !important; top: 0}
This has the same quadratic complexity as trincot discussed. If we wanted to we could fix that by first indexing with some sort of linear groupBy function, then doing a recursive lookup rather than a filter. I leave that as an exercise.
I'm trying to build a small React.js clone,
In the code snippet below, i made a simple component tree with a succession of functional components
function Text(props) {
return createElement('p', null, props.content)
}
function Hello(props) {
return createElement(Text, props.content, null)
}
function Home() { // this is the root element
return createElement('div', null,
createElement(Hello, {content: "hello world 1"}, null),
createElement(Hello, {content: "hello world 2"}, null)
)
}
The createElement function checks the type of the current node, assigning it an id and pushes it into the data Array. But to reconstitute the component tree, i need to get the parentId of each components that have been pushed into data.
I assume that if the value of i is zero, it means that the current element is the root element. But if not, how to find the id of the parent who created the current element ?
const data = [];
let i = 0;
function createElement(node, props, children) {
if(typeof node === "string") {
data.push({ name: node, id: i, parentId: i > 0 ? i : null });
i++;
};
if(typeof node === "function") {
let functionalComponent = constructFunctionComponent(node);
data.push({ name: node.name, id: i, parentId: i > 0 ? i : null });
i++;
createElement(functionalComponent(props)());
};
}
function constructFunctionComponent(fc) {
return (props) => (children) => fc(props, children);
}
Here is what a console.log displays if we execute the Home() function.
Here the parentId keys are obviously all false (except the first one because it is the root element)
// current output :
// note that here the parentId keys of each index are not correct (this is what i'm trying to resolve)
[
{ name: 'Home', id: 0, parentId: null },
{ name: 'Hello', id: 1, parentId: 1 },
{ name: 'Text', id: 2, parentId: 2 },
{ name: 'p', id: 3, parentId: 3 },
{ name: 'Hello', id: 4, parentId: 4 },
{ name: 'Text', id: 5, parentId: 5 },
{ name: 'p', id: 6, parentId: 6 },
{ name: 'div', id: 7, parentId: 7 }
]
// expected output:
// here, each parentId keys is a "reference" to the parent that added the index to the array
[
{ name: 'Home', id: 0, parentId: null },
{ name: 'Hello', id: 1, parentId: 7 },
{ name: 'Text', id: 2, parentId: 1 },
{ name: 'p', id: 3, parentId: 2 },
{ name: 'Hello', id: 4, parentId: 7 },
{ name: 'Text', id: 5, parentId: 4 },
{ name: 'p', id: 6, parentId: 5 },
{ name: 'div', id: 7, parentId: 0 }
]
I made a codeSandbox which contains the code. Any help would be greatly appreciated !
Here is a link to the codeSandbox example
Thanks,
As you're doing it now, the structure is being evaluated from the leaf nodes up, so the parent ID is not known at the time of each element's creation. You'll have to separate the generation of the IDs from the generation of the elements. Here's an example of what I mean (it's not pretty; you can probably come up with a more elegant way to do it):
function createElement(node, props, children) {
if(typeof node === "string") {
data.push({ name: node, id: props.id, parentId: props.parentId });
};
if(typeof node === "function") {
let functionalComponent = constructFunctionComponent(node);
data.push({ name: node.name, id: props.id, parentId: props.parentId });
createElement(functionalComponent(props)());
};
}
function Home() {
homeId = 0;
createElement
(
'div',
homeId,
createElement(Hello, {content: "hello 1", parentId: homeId, id: (hello1Id = ++homeId)}),
createElement(Hello, {content: "hello 2", parentId: homeId, id: (hello2Id = ++hello1Id)})
);
}
Now the ID is being created as part of the call to createElement, so it can be known and used in any further child creation.
I'm currently working through a problem that I'm having some trouble figuring out where I need to find a child node in an array of objects. The target could be one or many levels deep.
The issue is, once I find the object, I also need to push the path I took to get to that object into the resulting data array.
Currently, I have written code that can successfully find the child node:
const buildFullTree = (tree, cat, data = []) => {
let collection = [tree]
while (collection.length) {
let node = collection.shift()
if (node.id === cat.id) {
data.push(node)
}
collection.unshift(...node.children)
}
return data
}
However, this isn't sufficient in terms of getting the path taken to that object.
I'm pretty sure that I need to change this to a recursive depth-first search solution in order to achieve what I'm looking for, but I am not sure how to change the while loop to accomplish this.
If I understand your question correctly, then perhaps you could revise your path search function like so to achieve what you require:
const buildFullTree = (departmentTree, category, data = []) => {
const findPath = (node, category) => {
//If current node matches search node, return tail of path result
if (node.id === category.id) {
return [node]
} else {
//If current node not search node match, examine children. For first
//child that returns an array (path), prepend current node to that
//path result
for (const child of node.children) {
const childPath = findPath(child, category)
if (Array.isArray(childPath)) {
childPath.unshift(child)
return childPath
}
}
}
}
const foundPath = findPath(departmentTree, category)
// If search from root returns a path, prepend root node to path in
// data result
if (Array.isArray(foundPath)) {
data.push(departmentTree)
data.push(...foundPath)
}
return data
}
const departmentTree = {
id: 5,
title: 'department',
level: 1,
children: [{
id: 1,
parentId: 5,
title: 'category',
level: 2,
children: [{
id: 15,
parentId: 1,
title: 'subcategory',
level: 3,
children: []
}, {
id: 18,
parentId: 1,
level: 3,
title: 'subcategory',
children: []
}, {
id: 26,
parentId: 1,
level: 3,
title: 'subcategory',
children: [{
id: 75,
parentId: 26,
level: 4,
title: 'sub-subcategory',
children: []
}, {
id: 78,
parentId: 26,
level: 4,
title: 'sub-subcategory',
children: []
}]
}]
}, {
id: 23823,
title: 'category',
level: 2,
children: []
}, {
id: 9,
parentId: 5,
level: 2,
title: 'category',
children: [{
id: 48414,
parentId: 9,
level: 3,
title: 'subcategory',
children: []
}, {
id: 2414,
parentId: 9,
level: 3,
title: 'subcategory',
children: []
}, {
id: 42414,
parentId: 9,
level: 3,
title: 'subcategory',
children: [{
id: 2323213,
parentId: 42414,
level: 4,
title: 'sub-subcategory',
children: []
}, {
id: 322332,
parentId: 42414,
level: 4,
title: 'sub-subcategory',
children: []
}]
}]
}]
};
console.log('Path to 2323213:',
buildFullTree(departmentTree, {
id: 2323213
}).map(node => node.id).join(' -> '))
console.log('Path to 23823:',
buildFullTree(departmentTree, {
id: 23823
}).map(node => node.id).join(' -> '))
console.log('Path to -1 (non existing node):',
buildFullTree(departmentTree, {
id: -1
}).map(node => node.id).join(' -> '))
I have a flat array like this containing data objects with id and values. Every id will be unique
var data = [{
id: 1,
value: 'as',
parent: 2
}, {
id: 2,
value: 'sasa',
parent: 3
}, {
id: 3,
value: 'sasa',
parent:
}]
How can I create a hierarchical tree like "object" in JavaScript not an Array because I further want to access the object's elements like 3.2.value
{
id: 3,
value: 'sasa',
parent: '',
2: {
id: 2,
value: 'sasa',
parent: 3,
1: {
id: 1,
value: 'as',
parent: 2
}
}
}
You could take an iterative approach by using an object for collencting an create an object for id and parent at the same time to keep their relation.
At the end return the property which has the root as parent.
The result is lightly different, because you want to address the nodes by using their id as accessor.
var data = [{ id: 1, value: 'as', parent: 2 }, { id: 2, value: 'sasa', parent: 3 }, { id: 3, value: 'sasa', parent: '' }],
tree = function (data, root) {
return data.reduce(function (r, o) {
Object.assign(r[o.id] = r[o.id] || {}, o);
r[o.parent] = r[o.parent] || {};
r[o.parent][o.id] = r[o.id];
return r;
}, Object.create(null))[root];
}(data, '');
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I'm trying to figure out how to construct a hierachy object from some smaller objects that I have. Here's some example data:
{ id: 1, name: 'Jackson', parent: null },
{ id: 2, name: 'Jordan', parent: 1 },
{ id: 3, name: 'Jefferson', parent: 1 },
{ id: 4, name: 'Elvis', parent: 2 },
{ id: 5, name: 'Sally', parent: null },
{ id: 6, name: 'Eric', parent: 4 }
This would be constructed into a HIerarchy object that should look like so:
{
'1': {
name: 'Jackson',
children: {
'2': {
name: 'Jordan',
children: {
'4': {
name: 'Elvin',
children: {
'6': {
name: 'Eric',
children: { }
}
}
} },
'3': {
name: 'Jefferson',
children: { } }
}
},
'5': {
name: 'Sally',
children: { }
}
I'm really having a hard time figuring this out other then doing a for-loop for every id. (Ie: find all with null parent, find all with 1 parent, find all with 2 parent, etc...)
Here's my take on how to do this.
Create an object that will hold references to all the other objects, based on their key. This allows us to get a reference to each object by its key at the moment we need it as a parent of another element:
let register = {};
Create our output object:
let output = {};
Now let's go through each object in the array and add it to the structure:
// loop through the array
for (let el of elements) {
// clone the element, so we don't modify the original array
el = Object.assign({}, el);
// insert the clone into the register
register[el.id] = el;
if (!el.parent) { // if no parent is set, add it to the top level
output[el.id] = el;
} else { // otherwise, add it as a child of whatever the parent is
register[el.parent].children[el.id] = el;
}
// add a children property
el.children = {};
// remove the parent property
delete el.parent;
}
Remember that objects are always stored by reference, so modifying an object in the register object also modifies it in the output object.
Below is a working example.
let input = [{
id: 1,
name: 'Jackson',
parent: null
}, {
id: 2,
name: 'Jordan',
parent: 1
}, {
id: 3,
name: 'Jefferson',
parent: 1
}, {
id: 4,
name: 'Elvis',
parent: 2
}, {
id: 5,
name: 'Sally',
parent: null
}, {
id: 6,
name: 'Eric',
parent: 4
}];
let register = {};
let output = {};
// loop through the array
for (let el of input) {
// clone the element, so we don't modify the original array
el = Object.assign({}, el);
// insert the clone into the register
register[el.id] = el;
if (!el.parent) { // if no parent is set, add it to the top level
output[el.id] = el;
} else { // otherwise, add it as a child of whatever the parent is
register[el.parent].children[el.id] = el;
}
// add a children property
el.children = {};
// remove the parent property
delete el.parent;
}
console.log(JSON.stringify(output, undefined, 2));
Note that this will not work with circular references, or if the keys are out of order (i.e. the child appears before its parent).