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).
Related
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!
I have the following structure:
{
id: 15
name: "Power",
childCategories: [],
parent: {
id: 10,
name: "Cables",
parent: null,
stockItems: []
}
}
Ideally, what I would like to end up with is the following String structure:
Cables ⟶ Power
But being new to Javascript, I'm not sure how I would achieve this. I'm assuming it would be through recursion.
Looks like you want to "reverse" the way your data is structured ("children -> parents" to "parents -> children").
Your assumption about recursive function is right: in this example I tried to add more data, according to the description given.
const arr = [
{
childCategories: [],
id: 15,
name: "Power",
parent: {
id: 10,
name: "Cables",
parent: null,
stockItems: [],
}
},
{
childCategories: [],
id: 9,
name: "Gas",
parent: {
id: 20,
name: "Pipes",
parent: {
id: 33,
name: "Metal",
parent: null,
stockItems: [],
},
stockItems: [],
}
}
]
const res = []
for (const o of arr) {
recursiveFunct(o)
}
function recursiveFunct(o) {
if (o.parent) {
let parent = o.parent
delete o.parent
parent.children = o
recursiveFunct(parent)
} else {
res.push(o)
}
}
console.log(res)
For each object passed into the recursive function push its name into into an array (result).
If the object has a parent property that isn't null pass the parent object back into the function along with result.
If the parent is null reverse the array and join it up with a "⟶".
const obj = {
id: 153,
name: "Gold plated",
childCategories: [],
parent: {
id: 15,
name: "Power",
childCategories: [],
parent: {
id: 10,
name: "Cables",
parent: null,
stockItems: []
}
}
};
// Pass in the object, and initialise `result`
function getString(obj, result = []) {
// Push each name into the array
result.push(obj.name);
// If the parent is `null` return the joined array
// otherwise pass the parent object back into the
// function along with the result array
if (!obj.parent) {
return result.reverse().join(' ⟶ ');
} else {
return getString(obj.parent, result);
}
}
console.log(getString(obj));
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 wanted to know what is the best way to extract the same named object from a multi level nested object.
I currently have an object that looks like this, and i want to extract out the parentLocationCluster objects out of it.
const foo = {
id: '1',
name: 'boo',
parentLocationCluster: {
id: 1,
name: 'foo',
parentLocationCluster: {
id: 2,
name: 'fii',
parentLocationCLuster: {
id: 3,
name: 'faa',
},
},
},
};
Now I could just a nested if statement like so:
const { parentLocationCluster } = foo;
if(parentLocationCluster) {
//do something
if(parentLocationCluster.parentLocationCluster) {
//do something
}
}
but i feel this is significantly inefficient ( which is what i am doing at the moment). Also, the objects could vary with the number of nested parentLocationCluster objects i.e. an object could contain 10 levels of parentLocationClusters.
What would be the best way to do this?
The following snippet recursively accesses all nested clusters to any depth and does something with them.
const foo = {
id: '1',
name: 'boo',
parentLocationCluster: {
id: 1,
name: 'foo',
parentLocationCluster: {
id: 2,
name: 'fii',
parentLocationCluster: {
id: 3,
name: 'faa',
},
},
},
};
function readCluster(obj) {
const cluster = obj.parentLocationCluster
if (cluster) {
// Do something
console.log(cluster.name)
readCluster(cluster)
} else
return;
}
readCluster(foo);
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; }