How to fix node parent_id? - javascript

I’m setting up a data structure,and I want to fix node.
var data = [{
id: 1,
parent_id: 321
}, {
id: 2,
parent_id: 1
}, {
id: 3,
parent_id: 1
}, {
id: 4,
parent_id: 5
}, {
id: 5,
parent_id: 4
}];
const makeTree = (items, id, link = 'parent_id') => items.filter(item => item[link] == id).map(item => ({
...item,
children: makeTree(items, item.id)
}));
console.log(makeTree(data));
If the node has a reference at parent_id that is not present in the collection, my code will not return anything. And it is necessary to return that node (no matter what his parent is not there). Wanted result is when my {id: 1, parent_id: 321} has two children {id: 2, parent_id: 1}, {id: 3, parent_id: 1}.

You could take a staged approach by creating first the relation of all nodes, then get the depth of each node and filter by depth to get the largest. At the end, you get a part tree of the largest nodes.
function getDepth({ id, children }, seen = new Set) {
if (seen.has(id)) return 0; // prevent circular references
if (!children) return 1;
seen.add(id);
return children.reduce((c, o) => c + getDepth(o, seen), 1);
}
function getRelations(data) {
var t = {};
data.forEach((o) => {
Object.assign(t[o.id] = t[o.id] || {}, o);
t[o.parent_id] = t[o.parent_id] || {};
t[o.parent_id].children = t[o.parent_id].children || [];
t[o.parent_id].children.push(t[o.id]);
});
return t;
}
var data = [{ id: 1, parent_id: 321 }, { id: 2, parent_id: 1 }, { id: 3, parent_id: 1 }, { id: 4, parent_id: 5 }, { id: 5, parent_id: 4 }],
relations = getRelations(data),
longest = data
.map(({ id }) => [id, getDepth(relations[id])])
.reduce((r, a, i) => {
if (!i || r[0][1] < a[1]) return [a];
if (r[0][1] === a[1]) return r.push(a);
return r;
}, [])
.map(([id]) => relations[id]);
console.log(longest);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

How to iterate through a list using recursion

I've got a list like this below and I'm trying to get a list of all subnodes. I need to find all children and subchildren of a list. In this case it should return exact this list.
const data = [
{
id: 1,
parent: 0,
},
{
id: 2,
parent: 1,
},
{
id: 3,
parent: 1,
},
{
id: 4,
parent: 3,
}
];
I'm trying to choose whether or not call the function again in many ways but the result is always wrong.
const getNodes = (n) => {
let family = [];
for (let i of n) {
const sons = data.filter(x => x.parent === i.id);
if (sons.length !== 0) {
family.push(getNodes(sons));
} else {
family.push(i);
}
}
return family;
};
console.log(getNodes([data[0]]));
Let's transform that to a tree.
const data = [{
id: 1,
parent: 0,
},
{
id: 2,
parent: 1,
},
{
id: 3,
parent: 1,
},
{
id: 4,
parent: 3,
}
];
// first obj of nodes grouped by id
var obj_nodes = data.reduce(function(agg, item) {
agg[item.id] = { ...item, children: [] };
return agg;
}, {})
// console.log(obj_nodes)
// connecting edges (child parent relations)
data.forEach(function(item) {
var source = obj_nodes[item.id];
var destination = obj_nodes[item.parent];
destination && destination.children.push(source);
}, {})
var trees = Object.values(obj_nodes);
var result = trees[0]
console.log(result)
.as-console-wrapper {max-height: 100% !important}

How to make json with infinite parent child category from database

I have an SQLite database table
+---------------------------------------------------+
| id | Cat_Name | Parent_ID |
|---------------------------------------------------+
| 1 | Asset | NULL |
+---------------------------------------------------+
| 2 | Bank | 1 |
+---------------------------------------------------+
| 3 | Cash | 1 |
+---------------------------------------------------+
| 4 | Petty Cash | 3 |
+---------------------------------------------------+
| 5 | ABC Bank | 2 |
+---------------------------------------------------+
| 6 | Dollar Account | 2 |
+---------------------------------------------------+
i can fetch the data as below
[{ id: 1, Category_Name: "Asset", Parent_ID: 0},
{ id: 2, Category_Name: "Bank", Parent_ID: 1},
{ id: 3, Category_Name: "Cash", Parent_ID: 1},
{ id: 4, Category_Name: "Petty_Cash", Parent_ID: 3},
{ id: 5, Category_Name: "ABC_Bank", Parent_ID: 2},
{ id: 6, Category_Name: "Dollar_Account", Parent_ID: 2}]
In this table, category and subcategory created by the user, we can't assume how many parent and child categories will be in the table
Now I want pass the data as a nested javascript object to the front end
example
{Asset: {Bank: {ABC Bank: 5}, {Dollar Account: 6}
},
{Cash:{PettyCash: 4}, if any...}
}
Could anybody can help to get this result in the best way...
Thanks in advance
I suggest you change the design of the output object. I think the array approach would be better for the frontend.
const rawData = [
{ id: 1, Category_Name: "Asset", Parent_ID: 0},
{ id: 2, Category_Name: "Bank", Parent_ID: 1},
{ id: 3, Category_Name: "Cash", Parent_ID: 1},
{ id: 4, Category_Name: "Petty Cash", Parent_ID: 3},
{ id: 5, Category_Name: "ABC Bank", Parent_ID: 2},
{ id: 6, Category_Name: "Dollar Account", Parent_ID: 2},
{ id: 7, Category_Name: "Another Wallet", Parent_ID: 4},
];
const getParentDeep = (arr, targetId) => arr.find(({ id }) => id === targetId)
?? arr.flatMap(({ children }) => getParentDeep(children, targetId))
.filter(e => e)
.at(0);
const result = rawData
.sort(({ Parent_ID: a }, { Parent_ID: b }) => a - b)
.reduce((acc, { id, Category_Name, Parent_ID }) => {
const obj = { id, name: Category_Name, children: [] };
const parentObj = getParentDeep(acc, Parent_ID);
if (parentObj) parentObj.children.push(obj)
else acc.push(obj);
return acc;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0 }
The result will look like this:
[{
id: 1,
name: "Asset",
children: [{
id: 2,
name: "Bank",
children: [{
id: 5,
name: "ABC Bank",
children: []
}, {
id: 6,
name: "Dollar Account",
children: []
}]
}, {
id: 3,
name: "Cash",
children: [{
id: 4,
name: "Petty Cash",
children: [{
id: 7,
name: "Another Wallet",
children: []
}]
}]
}]
}]
Presented below is one possible way to achieve the desired objective. Admittedly, it is not very elegant (& possibly not the most-efficient).
Code Snippet
// helper method to recursively-add to object
const recurAdd = (arr, idx, res) => {
// when "idx" exceeds length of array "arr",
// simply return existing result "res" object
if (idx >= arr.length) return res;
// de-structure to access parent-id & id for current elt
const { Parent_ID, id } = arr[idx];
if (Parent_ID in res) {
// parent-id exists at current object,
// so, add "id" to same object (mutate)
res[Parent_ID][id] = {};
// make recursive call for "next" elt in "arr"
return recurAdd(arr, idx+1, res);
} else {
// find next-level object where current elt will fit
const foundIt = Object.values(res).map(obj => recurAdd(arr, idx, obj));
// NOTE: "obj" is part of "res" and it gets mutated
// if found, make recursive call
if (foundIt.some(x => x !== false)) return recurAdd(arr, idx+1, res);
};
// in case parent-id is not found, simply return false
return false;
};
// helper method to substitute "id" with "category names"
const recurNamify = (obj, myMap) => (
// reconstruct object from key-value pairs of intermediate result
Object.fromEntries(
// generate intermediate result of key-value pairs
Object.entries(obj)
.map(([k, v]) => (
// substitute key (ie, "id") with category-name
Object.keys(v).length === 0
? [myMap[k], k]
: [myMap[k], recurNamify(v, myMap)]
))
// when "v" is not an empty object, make recursive call
)
);
// transform the array into nested object
const myTransform = arr => {
// first transform "Number" to "string" for id and parent-id
// because JS-object keys are string type
const myArr = arr.map(ob => ({
...ob,
id: ob.id.toString(),
Parent_ID: ob.Parent_ID.toString()
}));
// generate a dictionary/map for "id" to category-name
const myMap = myArr.reduce(
(acc, itm) => {
acc[itm.id] = itm.Category_Name
return acc;
},
{}
);
// find the index of root (ie, parent id is zero)
const rIdx = myArr.findIndex(({ Parent_ID }) => Parent_ID === '0');
// obtain the root & mutate "arr" by removing the root
const [root] = myArr.splice(rIdx, 1);
// use the helper methods to transform
return recurNamify(recurAdd(myArr, 0, {[root.id]: {}}), myMap);
};
const rawData = [
{ id: 1, Category_Name: "Asset", Parent_ID: 0},
{ id: 2, Category_Name: "Bank", Parent_ID: 1},
{ id: 3, Category_Name: "Cash", Parent_ID: 1},
{ id: 4, Category_Name: "Petty_Cash", Parent_ID: 3},
{ id: 5, Category_Name: "ABC_Bank", Parent_ID: 2},
{ id: 6, Category_Name: "Dollar_Account", Parent_ID: 2}
];
console.log('transformed: ', myTransform(rawData));
.as-console-wrapper { max-height: 100% !important; top: 0 }
Explanation
Inline comments added to the snippet above.
PS: If you'd like to add value to stackoverflow community,
Please consider reading: What to do when my question is answered
Thank you !
Here's another linked list variation, but with bi-directional object references and JSON de-/serialization in acknowledgement of the client/server relationship:
The Stack Overflow code snippet virtual console doesn't show interactive object relationships like your browser's JS console, so copy and paste this into your JS console to see the relational references in the final linked list value.
/** Conceptually similar to CSV when stringified, but preserves JSON types */
function compact (keysOrMappedKeys, array) {
const inputKeys = [];
let outputKeys = [];
const keysAreMapped = Array.isArray(keysOrMappedKeys[0]);
if (keysAreMapped) {
for (const [keyIn, keyOut] of keysOrMappedKeys) {
inputKeys.push(keyIn);
outputKeys.push(keyOut);
}
}
else {
for (const key of keysOrMappedKeys) inputKeys.push(key);
outputKeys = inputKeys;
}
const rows = [];
for (const obj of array) {
const row = [];
for (const key of inputKeys) row.push(obj[key]);
rows.push(row);
}
return [outputKeys, rows];
}
// Not actually needed for this answer:
/** The reverse of the `compact` function */
function expand ([keys, rows]) {
return rows.map(array => {
const obj = {};
for (const [index, key] of keys.entries()) obj[key] = array[index];
return obj;
});
}
/** Expects keys in the order `[ownId, parentId, ...others]` */
function createLinkedObjectList ([keys, rows]) {
const map = new Map(rows.map(row => {
const obj = {};
const iter = keys.entries();
const [ownIdIndex] = iter.next().value;
const ownId = row[ownIdIndex];
const [parentIdIndex] = iter.next().value;
const parentId = row[parentIdIndex];
for (const [index, key] of iter) obj[key] = row[index];
return [ownId, {id: ownId, parentId, value: obj}];
}));
for (const obj of map.values()) {
const parent = map.get(obj.parentId);
if (typeof parent !== 'undefined') {
obj.parent = parent;
(parent.children ??= []).push(obj);
}
delete obj.parentId;
}
return [...map.values()];
}
// Use: On the server:
// From the SQLite db:
const input = [
{ id: 1, Category_Name: "Asset", Parent_ID: 0},
{ id: 2, Category_Name: "Bank", Parent_ID: 1},
{ id: 3, Category_Name: "Cash", Parent_ID: 1},
{ id: 4, Category_Name: "Petty_Cash", Parent_ID: 3},
{ id: 5, Category_Name: "ABC_Bank", Parent_ID: 2},
{ id: 6, Category_Name: "Dollar_Account", Parent_ID: 2},
];
// Optionally, rename the keys when compacting the data structure:
const mappedKeys = [
['id', 'id'], // The ID key needs to be first
['Parent_ID', 'parent'], // The parent ID key needs to be second
// The order of the remaining keys is simply preference:
['Category_Name', 'name'],
];
const compacted = compact(mappedKeys, input);
/*
Or, just use the original key names:
const keys = [
'id', // The ID key needs to be first
'Category_Name', // The parent ID key needs to be second
// The order of the remaining keys is simply preference:
'Parent_ID',
];
const compacted = compact(keys, input);
*/
// You can send this JSON string to the client
const json = JSON.stringify(compacted);
console.log(json); // [["id","parent","name"],[[1,0,"Asset"],[2,1,"Bank"],[3,1,"Cash"],[4,3,"Petty_Cash"],[5,2,"ABC_Bank"],[6,2,"Dollar_Account"]]]
// Use: On the client:
/* After receiving the json from the server:
const json = await getDataFromServer();
Expand it into a linked list with bi-directional references
between actual parent and children objects.
This is where the order of the keys matters: */
const list = createLinkedObjectList(compacted);
console.log(list); /* Looks like this:
[
{
id: 1,
value: { name: 'Asset' },
children: [
{ id: 2, ... },
{ id: 3, ... },
],
},
{
id: 2,
value: { name: 'Bank' },
parent: { id: 1, ... },
children: [
{ id: 5, ... },
{ id: 6, ... },
],
},
{
id: 3,
value: { name: 'Cash' },
parent: { id: 1, ... },
children: [
{ id: 4, ... },
],
},
{
id: 4,
value: { name: 'Petty_Cash' },
parent: { id: 3, ... },
},
{
id: 5,
value: { name: 'ABC_Bank' },
parent: { id: 2, ... },
},
{
id: 6,
value: { name: 'Dollar_Account' },
parent: { id: 2, ... },
},
]
*/

Change object property in nested array

I have an array:
const array = [
{ id: 1, parent_id: 0, visible: true },
{ id: 2, parent_id: 0, visible: true },
{ id: 3, parent_id: 1, visible: true },
{ id: 4, parent_id: 3, visible: true },
{ id: 5, parent_id: 4, visible: true },
{ id: 6, parent_id: 4, visible: true },
{ id: 7, parent_id: 3, visible: true },
{ id: 8, parent_id: 2, visible: true }
]
I want to create a function with arguments ID and ARRAY, that return new array with VISIBLE = FALSE for this ID and every nested child by PARENT_ID.
My effort is like this
const result = []
const findFitstHandler = (id, arr) => {
let j
for (let i in arr) {
if (arr[i].id === id) {
result.push(arr[i].id)
j = arr[i].id
}
}
findNested(j, arr)
return array.map(item => {
if (result.includes(item.id)) {
return {
...item,
visible: false
}
} else {
return item
}
})
}
const findNested = (id, arr) => {
for (let i in arr) {
if (arr[i].parent_id === id) {
result.push(arr[i].id)
findNested(arr[i].id, arr)
}
}
}
I'm sure there is a more elegant solution. Please, help me
Try with array map method:
const array = [
{ id: 1, parent_id: 0, visible: true },
{ id: 2, parent_id: 0, visible: true },
{ id: 3, parent_id: 1, visible: true },
{ id: 4, parent_id: 3, visible: true },
{ id: 5, parent_id: 4, visible: true },
{ id: 6, parent_id: 4, visible: true },
{ id: 7, parent_id: 3, visible: true },
{ id: 8, parent_id: 2, visible: true }
];
const getNewArray = (id, items) => items.map(item => {
if ([item.id, item.parent_id].includes(id)) {
item.visible = false;
}
return item;
});
console.log(getNewArray(4, array));
I would break apart the recursive code that finds the list of descendants from the code that does the data manipulation. Here's one possibility:
const descendants = (array, root) =>
[
root,
...array .filter (({parent_id}) => parent_id == root)
.flatMap (({id}) => descendants (array, id))
]
const changeBranch = (fn) => (array, root, keys = descendants (array, root)) =>
array .map (element => keys .includes (element .id) ? fn (element) : element)
const makeInvisible = changeBranch (
({visible, ...rest}) => ({...rest, visible: false})
)
const array = [{ id: 1, parent_id: 0, visible: true }, { id: 2, parent_id: 0, visible: true }, { id: 3, parent_id: 1, visible: true }, { id: 4, parent_id: 3, visible: true }, { id: 5, parent_id: 4, visible: true }, { id: 6, parent_id: 4, visible: true }, { id: 7, parent_id: 3, visible: true }, { id: 8, parent_id: 2, visible: true }];
console .log (makeInvisible (array, 4))
console .log (makeInvisible (array, 2))
.as-console-wrapper {min-height: 100% !important; top: 0}
descendants finds the ids of the root id supplied and all the nodes descendant from it inside your array.
changeBranch takes a function to transform a node and returns a function that takes an array and the root id and returns a new array containing either the result of applying that function (when the node is descendant from the root) or the original value (when it isn't.)
makeInvisible is the result of applying to changeBranch a function which sets visible to false. This is the final function you're looking for.
Note that if your list is cyclic and not hierarchical, this will not work. Your stack would likely overflow.

How to convert `pid` in json array into array of `children` form?

Raw array:
const data1 = [
{
id: 1,
pid: 0
},
{
id: 2,
pid: 1
},
{
id: 3,
pid: 2
}
]
How to convert pid in json array into array of children form?
How to turn him into:
[
{
id: 1,
pid: 0,
children: [
{
id: 2,
pid: 1,
children: [
{
id: 3,
pid: 2
}
]
}
]
}
]
-----------------------------------
He recognizes children by pid
How to write a function to do it?
thanks
const data = [
{
id: 1,
pid: 0
},
{
id: 4,
pid: 3
},
{
id: 2,
pid: 1
},
{
id: 3,
pid: 2
}
];
function toTree (data) {
data.forEach(function(item) {
delete item.children;
});
const map = {};
data.forEach(function(item) {
map[item.id] = item;
});
let val = [];
data.forEach(function(item) {
const parent = map[item.pid];
if(parent) {
(parent.children || (parent.children = [])).push(item);
} else {
val.push(item);
}
});
return val;
}
console.log(JSON.stringify(toTree(data)));
Refer to #chiliNUT answer, add a method :
const data1 = [
{
id: 1,
pid: 0
},
{
id: 4,
pid: 2
},
{
id: 5,
pid: 1
},
{
id: 3,
pid: 2
},
{
id: 2,
pid: 1
}
];
function toTree (data){
data.sort((a, b) => (a.pid - b.pid === 0) ? a.id - b.id : a.pid - b.pid);
const map = {}
data.forEach(item => (map[item.pid] || (map[item.pid] = []) ).push(item))
const mapArr = Object.values(map)
mapArr.reduce((a, b, index, arr) => {
if ( a[0].id === b[0].pid) { // There are still bugs here
a[0].children = b
}
return b;
})
return mapArr[0]
}
console.log(JSON.stringify(toTree(data1)));
data1.reduce((el1, el2)=>{el1.children = [el2]; return el2;});
const tree = [data1[0]];
You can use Array.reduce(el1, el2) It iterates over the array like map, except: For the first iteration, el1 and el2 are the first and second elements of the array, then for the iterations after, el1 is return value of the previous iteration, and el2 is the next element of the array. Unlike map, which operates on each element of the array, reduce uses each element of the array to generate a single return value.
data1.reduce((el1, el2)=>{el1.children = [el2]; return el2;});
So that appends all elements of data1 successively to the first element. Your final output should be an array, so
const tree = [data1[0]]
Follow up: if the data is not already sorted by id, you can sort it like this
data1.sort((el1, el2) => {return el1.id > el2.id ? 1 : -1});
const data1 = [
{
id: 1,
pid: 0
},
{
id: 2,
pid: 1
},
{
id: 3,
pid: 2
}
]
data1.reduce((a,b)=>{a.children=[b];return b;});
const tree = [data1[0]];
console.log(tree);
I think the best way is to use recursive to loop on each element and put as child of the previous one.
const data1 = [
{
id: 1,
pid: 0
},
{
id: 2,
pid: 1
},
{
id: 3,
pid: 2
}
];
function convert(arr){
let counter = 0;
let convertedArray = [];
function recursiveFunction(currentObject = null){
if(counter >= arr.length) return convertedArray;
if(currentObject == null){
currentObject = {
children: [arr[0]]
}
convertedArray.push(currentObject);
} else {
currentObject.children = [ arr[counter] ];
}
counter++;
return recursiveFunction(currentObject.children[0]);
}
return recursiveFunction();
}
let newData = convert(data1);
console.log(newData);

How to change an array of objects containing ids and children references into a nested tree structured object?

I would like to create an object with a tree structure from data that looks as follow:
nodes: [
{ name: "Ben", id: 1 next: [2, 3], depth: 0 },
{ name: "Mike", id: 2, next: [4, 5], depth: 1 },
{ name: "Jen", id: 3, next: [6], depth: 1 },
{ name: "Sue", id: 4, next [], depth: 2 },
{ name: "Jeff", id: 5, next: [], depth: 2 },
{ name: "Bob", id: 6, next: [], depth: 3 }
]
The tree like object would look like this:
root:
{ name: "Ben", children:
[
{ name: "Mike", children:
[
{ name: "Sue" },
{ name: "Jeff" }
]
},
{ name: "Jen", children:
[
{ name: "Bob" }
]
}
]
}
I can assign the root and go through the objects in the next array like this:
const root = { name: nodes[0].name };
root.children = [];
nodes[0].next.map(function (next) {
nodes.map((node, i) => {
if (next === node.id) {
root.children.push({name: nodes[i].name})
}
})
});
I'm not sure how to find next for the nodes pushed to the children array. The number of objects in the nodes array may vary, so the depth of children arrays may vary too. How can you create a new children array based on this variable and push the right properties to it?
First pass, map the nodes by id into something resembling the desired output. This has the added benefit of not mutating the original nodes objects
const idMap = nodes.reduce((map, { id, name }) => map.set(id, { name }), new Map())
Then iterate the nodes, reducing that to an array of roots
const roots = nodes.reduce((arr, node) => {
let obj = idMap.get(node.id)
if (node.next && node.next.length) {
obj.children = node.next.map(id => idMap.get(id))
}
if (node.depth === 0) {
arr.push(obj)
}
return arr
}, [])
Then find the first one with children
const root = roots.find(rootNode => rootNode.children)
const nodes = [{"name":"Ben","id":1,"next":[2,3],"depth":0},{"name":"Mike","id":2,"next":[4,5],"depth":1},{"name":"Jen","id":3,"next":[6],"depth":1},{"name":"Sue","id":4,"next":[],"depth":2},{"name":"Jeff","id":5,"next":[],"depth":2},{"name":"Bob","id":6,"next":[],"depth":3}]
const idMap = nodes.reduce((map, { id, name }) => map.set(id, { name }), new Map())
const roots = nodes.reduce((arr, node) => {
let obj = idMap.get(node.id)
if (node.next && node.next.length) {
obj.children = node.next.map(id => idMap.get(id))
}
if (node.depth === 0) {
arr.push(obj)
}
return arr
}, [])
const root = roots.find(rootNode => rootNode.children)
console.info(root)
You can use recursive function and go until next array length is 0 !!
var nodes = [
{ name: "Ben", id: 1, next: [2,3], depth: 0},
{ name: "Mike", id: 2, next: [4,5], depth: 1},
{ name: "Jen", id: 3, next: [6], depth: 1},
{ name: "Sue", id: 4, next: [], depth: 2},
{ name: "Jeff", id: 5, next: [], depth: 2 },
{ name: "Bob", id: 6, next: [], depth: 3 }
];
var root = {};
nodes.forEach(v => {
if(v.depth == 0) { // check root 0
root.name = v.name
if(v.next.length > 0 ) { // check next array has more than 0
root.children = []
findChild(root,v);
}
}
});
function findChild(root,c) {
c.next.forEach(v => {
var child = nodes.find(x => x.id === v)
var next = {name: child.name};
if(child.next.length > 0) {
next.children = [];
findChild(next,child);
}
root.children.push(next); // push to real root
})
}
console.log(root);
You should probably use an id lookup table:
const hash = {};
for(const node of nodes)
hash[node.id] = node;
for(const node of nodes) node.children = node.next.map(id => ((hash[id].parent = node), hash[id]));
const roots = nodes.filter(n => !n.parent);
As there could be multiple roots, this gives an array of roots.

Categories

Resources