Creating Hierarchy Using Id And Parentid
Solution 1:
Your problem requires traversing your dependency tree while keeping track of the depth. You can solve the problem using a depth-first-traversal implemented using recursion and incrementing the depth.
Update: Alright, you wanted some more explanation, some more explanation you'll get.
Also, can you provide some links to where I can study these kinds of concepts? Your approach is very new to me
This problem cam be solved using recursion. I'll explain the code and drop in some links to the concepts.
Transform sets
to hashMap
The first thing I'm doing is transforming your sets
object to another object which I'm calling hashMap
. I'm transforming it to this form because it will make "look-ups" faster when we're creating the tier
object.
Here is the sets
object for reference:
const sets = {
'dataSet1': {
'ID': '1',
'ParentID': '0'
},
'dataSet2': {
'ID': '2',
'ParentID': '1'
},
'dataSet3': {
'ID': '3',
'ParentID': '1'
},
'dataSet4': {
'ID': '4',
'ParentID': '3'
}
};
And I perform this transformation:
const hashMap = (Object// 1
.keys(sets)
// 2
.map(datasetKey => ({
originalKey: datasetKey,
id: sets[datasetKey].ID,
parentId: sets[datasetKey].ParentID
}))
// 3
.reduce((hashMap, value) => {
// 4const hashMapKey = 'hasParentId' + value.parentId;
// 5if (!hashMap[hashMapKey]) {
hashMap[hashMapKey] = [];
}
// 6
hashMap[hashMapKey].push(value);
// 7return hashMap;
}, {})
);
which results in this object:
{"hasParentId0":[{"originalKey":"dataSet1","id":"1","parentId":"0"}],"hasParentId1":[{"originalKey":"dataSet2","id":"2","parentId":"1"},{"originalKey":"dataSet3","id":"3","parentId":"1"}],"hasParentId3":[{"originalKey":"dataSet4","id":"4","parentId":"3"}]}
So step by step:
- Use
Object.keys
to get an array of keys from the objectsets
- Use
Array.prototype.map
to map each key of the array create from callingObject.keys
to a new object. This object has 3 properties:originalKey
,id
,parentId
- Use
Array.prototype.reduce
to reduce the array of mapped objects (created in the last step) into a single object. The first parameter ofArray.prototype.reduce
is a "reducer" function that takes two arguments. The first argument of the "reducer" function is an "accumulator" and the second argument is an item from your array. The reducer will be called multiple times, one call for each item in the array. The reducer function returns an accumulator and that accumulator will be used in the next call of this inner function (so all the items of the array reduce into the accumulator). The second argument ofArray.prototype.reduce
is the initial accumulator. In this case, the initial accumulator is an empty object{}
. - Inside the reducer function, we create a string
hashMapKey
. This string will be used as the key to the accumulator. - This transformation expects every
hashMap[hashMapKey]
to be an array. Here we check to see if the value athashMap[hashMapKey]
is an array and if it isn't we make it one. - Now that we know there is an array at
hashMap[hashMapKey]
, we can push thevalue
on that array. Remember that thevalue
is an object we mapped from step 2. - Return the modified
hashMap
to be used as the "accumulator" of the next "reducer" call.
When the reduction is finished, the hashMap
contains the correct transformation. The transformation to sets
to hashMap
is required for fast lookups in the next step.
Transform hashMap
to tier
The above structure has an implied graph where each node (aka point) is an object from sets
and each edge is a directed relationship via the property ParentID
.
In order to create the structure you want, you need to perform a depth-first-traversal traversing this graph while keeping track of the depth.
Here is the code:
// 1const tier = {};
let maxDepth = 0;
// 2functiontraverse(currentParentId, depth) {
// 3if (depth > maxDepth) {
maxDepth = depth;
}
// 4const childrenOfCurrentParent = hashMap['hasParentId' + currentParentId]
// 5if (!childrenOfCurrentParent) {
return;
}
// 6
childrenOfCurrentParent.forEach(value => {
// 7const tierKey = 'tier' + depth;
if (!tier[tierKey]) {
tier[tierKey] = [];
}
// 8
tier[tierKey].push(value);
// 9traverse(value.id, depth + 1);
});
}
- create an empty object to store the tiers. Optionally create a variable for the hold the max depth. This is useful if you want to convert the tiers object into a tiers array.
- define a function
traverse
that takes two arguments:currentParentId
anddepth
. ThecurrentParentId
is the key used to identify the id of the current parent. This function is initially called with acurrentParentId
of0
and adepth
of0
. This will make more sense once we look at the implementation of the code. - Reassign
maxDepth
- Grab the
childrenOfCurrentParent
from the lookup table. See why we made it? Looking up values in this table is very fast :) - If there isn't any
childrenOfCurrentParent
, don't do anything. - Use
Array.prototype.forEach
to iterate through thechildrenOfCurrentParent
. Thevalue
again is thevalue
of the mapped objects from the transformation. - create a
tierKey
to store thevalue
in. If the array isn't initialized, initialize it. - push the
value
- Use recursion, call the
traverse
function again from within itself changing thecurrentParrentId
and incrementing thedepth
. Repeat from step 3 until there are no more items to be placed. This is depth-first-traversal.
Transforming tier
to tierArray
I think this one is self-explanatory. Since the tiers are a set of ordered items, it might be better to store it in an array. This is why we kept track of the maxDepth
.
Working snippet
const sets = {
'dataSet1': {
'ID': '1',
'ParentID': '0'
},
'dataSet2': {
'ID': '2',
'ParentID': '1'
},
'dataSet3': {
'ID': '3',
'ParentID': '1'
},
'dataSet4': {
'ID': '4',
'ParentID': '3'
}
};
// `hashMap` is a data transform// see the `console.log` of this transformconst hashMap = (Object// grab the keys of the object `sets`
.keys(sets)
// then map every key to a new object
.map(datasetKey => ({
originalKey: datasetKey,
id: sets[datasetKey].ID,
parentId: sets[datasetKey].ParentID
}))
// reduce the new objects into a single object
.reduce((hashMap, value) => {
// create a key to store the `value`const hashMapKey = 'hasParentId' + value.parentId;
// if `hashMap[hashMapKey]` isn't initialized yet,// initialize it to an arrayif (!hashMap[hashMapKey]) {
hashMap[hashMapKey] = [];
}
// then push the value (the objects created above)
hashMap[hashMapKey].push(value);
// return the hashMapreturn hashMap;
}, {})
);
console.log('hashMap', hashMap);
// create an object to store the `tier`s in.const tier = {};
// keep track of the `maxDepth` in case you want to transform `tier` to an arraylet maxDepth = 0;
// this is a recursive function to traverse your graph// this implements a depth-first-traversal keeping track of the `depth`functiontraverse(currentParentId, depth) {
if (depth > maxDepth) {
maxDepth = depth;
}
const childrenOfCurrentParent = hashMap['hasParentId' + currentParentId]
// if there are no children of the currentParentif (!childrenOfCurrentParent) {
return;
}
childrenOfCurrentParent.forEach(value => {
const tierKey = 'tier' + depth;
if (!tier[tierKey]) {
tier[tierKey] = [];
}
tier[tierKey].push(value);
traverse(value.id, depth + 1);
});
}
// call the function to start the traversal// using 0 as the first `currentParentId` and// using 0 as the first `depth` valuetraverse(0, 0);
// the tier object is now createdconsole.log('tier', tier);
// but it might be more natural to have `tier` as an arrayconst tierArray = [];
for (let i = 0; i < maxDepth; i += 1) {
tierArray[i] = tier['tier' + i];
}
console.log('tierArray', tierArray);
Phew, what an answer. Hope this helps!
Solution 2:
The problem with your if condition in your for loop. It only proccess the top level element if (datasetobj[x]['ParentID'] == 0) only processes parent id 0
.
To get what you're after dynamically create hierarchy arrays and remove the if condition. Do something like:
var tiers = {};
var sets = {
'dataSet1': {
'ID': '1',
'ParentID': '0'
},
'dataSet2': {
'ID': '2',
'ParentID': '1'
},
'dataSet3': {
'ID': '3',
'ParentID': '1'
},
'dataSet4': {
'ID': '4',
'ParentID': '3'
}
};
var datasetobj = sets;
var hierarchy = tiers;
for (x in datasetobj) {
if (datasetobj[x]['ParentID']) {
var idx = 'tier'+datasetobj[x]['ParentID'];
if(!hierarchy[idx])
hierarchy[idx]=[];
hierarchy[idx].push({
'parentid': datasetobj[x]['ParentID'],
'id': datasetobj[x]['ID']
});
}
};
console.log(tiers);
Post a Comment for "Creating Hierarchy Using Id And Parentid"