create a pruned copy of tree in javascript - javascript

I'm trying to create a pruned version of the tree below where I have the source data/tree:
const treeData = [{
title: '0-0',
key: '0-0',
children: [{
title: '0-0-0',
key: '0-0-0',
children: [
{ title: '0-0-0-0', key: '0-0-0-0', children: [] },
{ title: '0-0-0-1', key: '0-0-0-1', children: [] },
{ title: '0-0-0-2', key: '0-0-0-2', children: [] },
],
}, {
title: '0-0-1',
key: '0-0-1',
children: [
{ title: '0-0-1-0', key: '0-0-1-0', children: [] },
{ title: '0-0-1-1', key: '0-0-1-1', children: [] },
{ title: '0-0-1-2', key: '0-0-1-2', children: [] },
],
}, {
title: '0-0-2',
key: '0-0-2',
children: []
}],
}, {
title: '0-1',
key: '0-1',
children: [
{ title: '0-1-0-0', key: '0-1-0-0', children: [] },
{ title: '0-1-0-1', key: '0-1-0-1', children: [] },
{ title: '0-1-0-2', key: '0-1-0-2', children: [] },
],
}, {
title: '0-2',
key: '0-2',
children: []
}];
and an array of leaf nodes as inputs.
const leafNodes = ['0-0-1-2', '0-1-0-1', '0-1-0-2']
Given this input, I would want this pruned tree that uses the leaf nodes to build all paths from the root to each leaf:
const pruned [{
title: '0-0',
key: '0-0',
children: [{
title: '0-0-1',
key: '0-0-1',
children: [
{ title: '0-0-1-2',
key: '0-0-1-2',
children: []
}
]
}]
}, {
title: '0-1',
key: '0-1',
children: [{
title: '0-1-0-1',
key: '0-1-0-1',
children: []
}, {
title: '0-1-0-2',
key: '0-1-0-2',
children: []
}]
}]
I was thinking of building the copy node by node instead of copying the data source and then taking away the paths not buildable based on the array/list of leaf nodes as I figured that would be the easiest to grok for maintainability purposes but even then, I'm puzzled as to how to coordinate the process, especially when accounting for the middle nodes that have already been added to my copy tree in progress as would be the case for '0-1-0-1' and '0-1-0-2'. At any rate, I've been stumped for awhile and threw my hands up. The code referenced is javascript but I'd be open to answers in other languages similar enough to javascript.

You could build new array/objects by finding the target key and collect all objects to it by returning the arrays with the necessary nodes.
function getParts(array, leafes) {
var result = [];
array.forEach(o => {
var children;
if (leafes.includes(o.key)) {
result.push(o);
return;
}
children = getParts(o.children, leafes);
if (children.length) {
result.push(Object.assign({}, o, { children }));
}
});
return result;
}
const
treeData = [{ title: '0-0', key: '0-0', children: [{ title: '0-0-0', key: '0-0-0', children: [{ title: '0-0-0-0', key: '0-0-0-0', children: [] }, { title: '0-0-0-1', key: '0-0-0-1', children: [] }, { title: '0-0-0-2', key: '0-0-0-2', children: [] }] }, { title: '0-0-1', key: '0-0-1', children: [{ title: '0-0-1-0', key: '0-0-1-0', children: [] }, { title: '0-0-1-1', key: '0-0-1-1', children: [] }, { title: '0-0-1-2', key: '0-0-1-2', children: [] }] }, { title: '0-0-2', key: '0-0-2', children: [] }] }, { title: '0-1', key: '0-1', children: [{ title: '0-1-0-0', key: '0-1-0-0', children: [] }, { title: '0-1-0-1', key: '0-1-0-1', children: [] }, { title: '0-1-0-2', key: '0-1-0-2', children: [] }] }, { title: '0-2', key: '0-2', children: [] }],
leafNodes = ['0-0-1-2', '0-1-0-1', '0-1-0-2'],
pruned = getParts(treeData, leafNodes);
console.log(pruned);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

Is there a way to concatenate parent's value in array of deep nested objects

I have an array of nested objects. I need to update the id property of each node by concatenating all its parent names.
id should be value of the name property of current node joined with name property
od it's parents separated by '/'
treeData = [{
name: 'Infiniti',
id: '',
children: [{
name: 'G50',
id: '',
children: [{
name: 'Pure AWD',
id: ''
},
{
name: 'Luxe',
id: ''
},
],
},
{
name: 'QX50',
id: '',
children: [{
name: 'Pure AWD',
id: ''
},
{
name: 'Luxe',
id: ''
},
],
},
],
},
{
name: 'BMW',
id: '',
children: [{
name: '2 Series',
id: '',
children: [{
name: 'Coupé',
id: ''
},
{
name: 'Gran Coupé',
id: ''
},
],
},
{
name: '3 Series',
id: '',
children: [{
name: 'Sedan',
id: ''
},
{
name: 'PHEV',
id: ''
},
],
},
],
},
];
Expected Outcome
[{
name: 'Infiniti',
id: 'Infiniti',
children: [{
name: 'G50',
id: 'Infiniti/G50',
children: [{
name: 'Pure AWD',
id: 'Infiniti/G50/Pure AWD'
},
{
name: 'Luxe',
id: 'Infiniti/G50/Luxe'
},
],
},
{
name: 'QX50',
id: 'Infiniti/QX50',
children: [{
name: 'Pure AWD',
id: 'Infiniti/QX50/Pure AWD'
},
{
name: 'Luxe',
id: 'Infiniti/QX50/Luxe'
},
],
},
],
},
{
name: 'BMW',
id: 'BMW',
children: [{
name: '2 Series',
id: 'BMW/2 Series',
children: [{
name: 'Coupé',
id: 'BMW/2 Series/Coupé'
},
{
name: 'Gran Coupé',
id: 'BMW/2 Series/Gran Coupé'
},
],
},
{
name: '3 Series',
id: 'BMW/3 Series',
children: [{
name: 'Sedan',
id: 'BMW/3 Series/Sedan'
},
{
name: 'PHEV',
id: 'BMW/3 Series/PHEV'
},
],
},
],
},
];
I tried to use Array.prototype.reduce(), but I am unable to get the previous value to concatinate.
function updateTreeData(array) {
return array.reduce((returnValue, currentValue) => {
if (currentValue.children != null) {
returnValue.push(Object.assign({}, currentValue, {
children: this.updateTreeData(currentValue.children)
}))
}
return returnValue
}, []);
}
console.log(updateTreeData(treeData))
You can try the recursion approach
const treeData=[{name:"Infiniti",id:"",children:[{name:"G50",id:"",children:[{name:"Pure AWD",id:""},{name:"Luxe",id:""}]},{name:"QX50",id:"",children:[{name:"Pure AWD",id:""},{name:"Luxe",id:""}]}]},{name:"BMW",id:"",children:[{name:"2 Series",id:"",children:[{name:"Coupé",id:""},{name:"Gran Coupé",id:""}]},{name:"3 Series",id:"",children:[{name:"Sedan",id:""},{name:"PHEV",id:""}]}]}];
const populateId = (list, currentId) => {
for(const item of list) {
if(!currentId) {
item.id = item.name
} else {
item.id = currentId + '/' + item.name
}
if(item.children) {
populateId(item.children, item.id)
}
}
}
populateId(treeData)
console.log(treeData)

How to deep merge two collections by duplicate key in JavaScript/Lodash?

I would like to merge two collections by duplicate key in javascript, here is example collections:
let collection1 = [
{
title: 'Overview',
key: 'Test-overview',
isLeaf: true
},
{
title: 'Folder 1',
key: 'Test-Folder_1',
children: [
{
title: 'Folder 1 Content 1',
key: 'Test-Folder_1-Content_1',
isLeaf: true,
},
],
}
]
let collection2 = [
{
title: 'Folder 1',
key: 'Test-Folder_1',
children: [
{
title: 'Sub Folder 1 in Folder 1',
key: 'Test-Folder_1-Sub_Folder_1',
children: [
{
title: 'Sub Folder 1 Conetent',
key: 'Test-Folder_1-Sub_Folder_1-Content',
isLeaf: true,
},
],
},
],
}
]
and this is example output:
let exampleOutput = [
{
title: 'Overview',
key: 'Test-overview',
isLeaf: true
},
{
title: 'Folder 1',
key: 'Test-Folder_1',
children: [
{
title: 'Folder 1 Content 1',
key: 'Test-Folder_1-Content_1',
isLeaf: true,
},
{
title: 'Sub Folder 1 in Folder 1',
key: 'Test-Folder_1-Sub_Folder_1',
children: [
{
title: 'Sub Folder 1 Conetent',
key: 'Test-Folder_1-Sub_Folder_1-Content',
isLeaf: true,
},
],
},
],
}
]
How can I achieve this output with Javascript? I tried with Lodash _.merge and _.mergeWith but the output is not what I want.
I also tried this link: Merge JavaScript objects in array with same key answer by #BenG but it only able to merge the first layer of the collections, which mean if I have collection3 that contain another content in Test-Folder_1-Sub_Folder_1, it will be replaced by the first layer of the new collection.
You can use recursion:
let collection1 = [{title: 'Overview', key: 'Test-overview', isLeaf: true}, {title: 'Folder 1', key: 'Test-Folder_1', children: [{title: 'Folder 1 Content 1', key: 'Test-Folder_1-Content_1', isLeaf: true, },],}]
let collection2 = [{title: 'Folder 1', key: 'Test-Folder_1', children: [{title: 'Sub Folder 1 in Folder 1', key: 'Test-Folder_1-Sub_Folder_1', children: [{title: 'Sub Folder 1 Conetent', key: 'Test-Folder_1-Sub_Folder_1-Content', isLeaf: true,},],},],}]
function merge(collections){
var formed = {}
for (var i of collections){
key = JSON.stringify(Object.keys(i).filter(x => x != 'children').map(x => [x, i[x]]))
if (!(key in formed)){
formed[key] = []
}
formed[key] = [...formed[key], ...('children' in i ? i.children : [])]
}
return Object.keys(formed).map(x => ({...Object.fromEntries(JSON.parse(x)), ...(formed[x].length ? {'children':merge(formed[x])} : {})}))
}
console.log(merge([...collection1, ...collection2]))

How to convert flat array containing a child "path" array into a nested array of objects based on the path?

I have an array of objects, each containing a path property which holds the value of "paths" to which I'd like to map the array elements to.
let myData = [
{
path: ['Movies', 'Comedies', 'TopRanked'],
name: 'The Hangover',
id: '1',
},
{
path: ['Movies', 'Comedies', 'TopRanked'],
name: 'Eurotrip',
id: '2',
},
{
path: ['Movies', 'Action'],
name: 'Need for Speed',
id: '3',
},
{
path: ['Life'],
name: 'Not so bad',
id: '4',
},
{
path: ['Life', 'Financial', 'Income'],
name: 'Making Hundreds',
id: '5',
},
{
path: ['Life', 'Financial', 'Income'],
name: 'Making Thousands',
id: '6',
},
{
path: ['Life', 'MonthlySpent'],
name: 'Just a little bit',
id: '7',
},
{
path: ['Life', 'MonthlySpent'],
name: 'Living large',
id: '8',
},
];
console.log(myData);
Essentially, the result I am looking for is a breakdown of that array into as many as nested arrays as needed (relative to all possible available paths), with each retaining its "type" - either a parent or an item. So the desired output is like so:
let myTree = [
{
name: 'Movies',
type: 'parent',
children: [
{
name: 'Comedies',
type: 'parent',
children: [
{
name: 'TopRanked',
type: 'parent',
children: [
{
name: 'The Hangover',
type: 'item',
id: 1,
path: ['Movies', 'Comedies', 'TopRanked']
},
{
name: 'Eurotrip',
type: 'item',
id: 2,
path: ['Movies', 'Comedies', 'TopRanked'],
}
]
},
]
},
{
name: 'Action',
type: 'parent',
children: [
{
name: 'Need for Speed',
type: 'item',
id: 3,
path: ['Movies', 'Action'],
},
]
}
]
},
{
name: 'Life',
type: 'parent',
children: [
{
name: 'Not so bad',
type: 'item',
id: 4,
path: ['Life'],
},
{
name: 'Financial',
type: 'parent',
children: [
{
name: 'Income',
type: 'parent',
children: [
{
name: 'Making Hundreds',
type: 'item',
id: 5,
path: ['Life', 'Financial', 'Income'],
},
{
name: 'Making Thousands',
type: 'item',
id: 6,
path: ['Life', 'Financial', 'Income'],
}
]
}
]
},
{
name: 'MonthlySpent',
type: 'parent',
children: [
{
name: 'Just a little bit',
type: 'item',
id: 7,
path: ['Life', 'MonthlySpent'],
},
{
name: 'Living Large',
type: 'item',
id: 8,
path: ['Life', 'MonthlySpent'],
}
]
}
]
}
]
console.log(myTree);
I tried the following, and while the tree structure is created, the "item"-types are not placed as the array-value of the last nested "parent" type:
function treeData(data) {
var result = [],
hash = { _: { children: result } };
data.forEach(function (object) {
object.path.reduce(function (o, p) {
if (!o[p]) {
o[p] = { _: { name: p, children: [] } };
o._.children.push(o[p]._);
}
return o[p];
}, hash)._.name = object.name;
});
return result;
}
Would appreciate a working solution, as I am wracking my head and can't find one. Tnnx.
The approach below follows a similar pattern to your code i.e. loop every object, but instead of a reduce simply loops every item in path and creates a branch off the root. When there are no more 'branches' then add the original object. See the comments.
let myData = data();
let myTree = treeData(data);
console.log(myTree);
function treeData(data) {
let root = {"children": []} // create origin
for (obj of myData) { // loop items in the data
obj.type = "Item"; // add a property to suit your output
let tree = root; // start at root every object
for (path of obj.path) { // loop over items in path
let branch = tree.children.find(k => k.name == path); // look for branch
if (!branch) { // if no branch, create one
branch = {"name": path, "type": "parent", "children": []}
tree.children.push(branch); // push this into children of current level
}
tree = branch; // set tree to branch before processing next item in path
}
tree.children.push(obj); // add the item to the hierarchy after path is exhausted
}
return root.children; // return children of the root to suit your output
}
function data() {
return [
{
path: ['Movies', 'Comedies', 'TopRanked'],
name: 'The Hangover',
id: '1',
},
{
path: ['Movies', 'Comedies', 'TopRanked'],
name: 'Eurotrip',
id: '2',
},
{
path: ['Movies', 'Action'],
name: 'Need for Speed',
id: '3',
},
{
path: ['Life'],
name: 'Not so bad',
id: '4',
},
{
path: ['Life', 'Financial', 'Income'],
name: 'Making Hundreds',
id: '5',
},
{
path: ['Life', 'Financial', 'Income'],
name: 'Making Thousands',
id: '6',
},
{
path: ['Life', 'MonthlySpent'],
name: 'Just a little bit',
id: '7',
},
{
path: ['Life', 'MonthlySpent'],
name: 'Living large',
id: '8',
},
];
}
.as-console-wrapper { max-height: 100% !important; top: 0; }

Javascript group by array of objects (complicated objects)

After a lot of tries and search, I couldn't solve my following problem:
I have the following array
[
{text: "text", title: "title", cid: "cid", active: true, nodes: [
{title: "subTitle", text: "subText", cid: "cid", active: true, nodes: [
{text:"123", title:"321"},
{text:"456", title:"654"},
{text:"789", title:"765"}
]},
{title: "subTitle", text: "subText2", cid: "cid2", active: true, nodes: [
{text:"testText", title:"testTitle1"},
{text:"testText", title:"testTitle2"},
{text:"testText", title:"testTitle3"}
]},
{title: "subTitle", text: "subText3", cid: "cid3", active: true, nodes: [
{text:"ycycy", title:"asd"},
{text:"nyd", title:"yf"},
{text:"xfg", title:"qq"}
]},
{title: "anotherSubTitle", text: "subText4", cid: "cid4", active: true, nodes: [
{text:"fff", title:"hhh"},
{text:"xxx", title:"sss"},
{text:"hhh", title:"jjj"}
]}
]}
]
I want to reach the following format:
[
{text: "text", title: "title", cid: "cid", active: true, nodes: [
{title: "subTitle", text: "subText", cid: "cid", active: true, nodes: [
{text:"123", title:"321"},
{text:"456", title:"654"},
{text:"789", title:"765"},
{text:"testText", title:"testTitle1"},
{text:"testText", title:"testTitle1"},
{text:"testText", title:"testTitle1"},
{text:"ycycy", title:"asd"},
{text:"nyd", title:"yf"},
{text:"xfg", title:"qq"}
]},
{title: "anotherSubTitle", text: "subText4", cid: "cid4", active: true, nodes: [
{text:"fff", title:"hhh"},
{text:"xxx", title:"sss"},
{text:"hhh", title:"jjj"}
]}
]}
]
I tried array.reduce and to loop through the array but each time I got a wrong result...
Any suggestion plz?
You could take a nested grouping by a property for all levels.
const
groupBy = (array, key) => array.reduce((r, { nodes, ...o }) => {
let temp = r.find(q => q[key] === o[key]);
if (!temp) r.push(temp = o);
if (nodes) (temp.nodes ??= []).push(...groupBy(nodes, key));
return r;
}, []),
data = [{ text: "text", title: "title", cid: "cid", active: true, nodes: [{ title: "subTitle", text: "subText", cid: "cid", active: true, nodes: [{ text: "123", title: "321" }, { text: "456", title: "654" }, { text: "789", title: "765" }] }, { title: "subTitle", text: "subText2", cid: "cid2", active: true, nodes: [{ text: "testText", title: "testTitle1" }, { text: "testText", title: "testTitle2" }, { text: "testText", title: "testTitle3" }] }, { title: "subTitle", text: "subText3", cid: "cid3", active: true, nodes: [{ text: "ycycy", title: "asd" }, { text: "nyd", title: "yf" }, { text: "xfg", title: "qq" }] }] }],
result = groupBy(data, 'title');
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

How do I map through a variable in React JS

I was using react-tree-graph and I was creating a data object but I want to use my backend api to fill in the properties of the data object. I tried using the .map method to loop through my api but I couldnt figure out how to get it to work. Here's the data object and below is my object from my api.
let data = {
name: 'Water',
children: [
{
name: 'Earth',
children: [
{
name: 'Air',
children: [
{
name: 'Fire',
children: [],
},
{
name: 'Fire2',
children: [],
},
],
},
{
name: 'Air2',
children: [
{
name: 'Fire3',
children: [],
},
{
name: 'Fire4',
children: [],
},
],
},
],
},
{
name: 'Earth2',
children: [
{
name: 'Air3',
children: [
{
name: 'Fire5',
children: [],
},
{
name: 'Fire6',
children: [],
},
],
},
{
name: 'Air4',
children: [
{
name: 'Fire7',
children: [],
},
{
name: 'Fire8',
children: [],
},
],
},
],
},
],
};
Instead of the strings I want to map through this variable and fill it in with object as such
flower= {water: "name1",air: "name2",air2: "name3",air3: "name4",air4: "name5",earth: "name6",earth2: "name7",fire: "name8",fire2: "name9",fire3: "name10",fire4: "name11",fire5: "name12",fire6: "name13",fire7: "name14",fire8: "name15"}
I just wanted to know how I could use this object and map through it to fill in the different properties inside the data object
Here is a solution for you question, Ive worked with recursive.
let data = {
name: 'Water',
children: [
{
name: 'Earth',
children: [
{
name: 'Air',
children: [
{
name: 'Fire',
children: [],
},
{
name: 'Fire2',
children: [],
},
],
},
{
name: 'Air2',
children: [
{
name: 'Fire3',
children: [],
},
{
name: 'Fire4',
children: [],
},
],
},
],
},
{
name: 'Earth2',
children: [
{
name: 'Air3',
children: [
{
name: 'Fire5',
children: [],
},
{
name: 'Fire6',
children: [],
},
],
},
{
name: 'Air4',
children: [
{
name: 'Fire7',
children: [],
},
{
name: 'Fire8',
children: [],
},
],
},
],
},
],
};
let counter = 1;
function recursiveNaming(data) {
return data.children.reduce((acc, curr) => {
let obj;
let currentObj = {...acc, [curr.name]:`name${counter++}`};
if(curr.children.length) {
obj = recursiveNaming(curr);
}
return {...currentObj, ...obj };
},{})
};
console.log(recursiveNaming(data))
Hope it worth it....

Categories

Resources