I have treeFlatNode array i want to structure it in tree format. or can i display this array in tree directly in angular.
data=[
{
expandable: true
level: 0
name: "2021-12-31"
path: null
},
{
expandable: false
level: 2
name: "A.txt"
path: "2021-12-31/B/C/A.txt"
}
]
required format
tree=[
name:"2021-12-03",
children:[
name:"B",
children:[{
name:"C"
children:[{
name:"A.txt"
children:[]
}]
}]
]
]
You could use an object (map) that maps a (sub)path to a node in the final tree. If it doesn't exist yet, it is added to the parent's children.
As your tree structure actually represents a forest (there can be multiple roots), I would name the result variable forest instead of tree
Snippet:
function toForest(data) {
const roots = [];
const map = {};
for (const obj of data) {
let key = "";
let children = roots;
for (const name of (obj.path ?? obj.name).split("/")) {
let child = map[key += "/" + name];
if (!child) children.push(map[key] = child = { name, children: [] });
({children} = child);
}
}
return roots;
}
// Example run
let data = [{expandable: true,level: 0,name: "2021-12-31",path: null}, {expandable: false,level: 2,name: "A.txt",path: "2021-12-31/B/C/A.txt"}];
let forest = toForest(data);
console.log(forest);
So, to transform your data structure to the desired one, you can use following function (with comments =) ):
transform(data){
const tree = [];
for (let node of data) {
// If there's no path it's a parent node
// but add it only if it doesn't exist yet
if (node.path === null && tree.every(n => n.name !== node.name)) {
tree.push({ name: node.name, children: [] });
continue;
}
// Extract name of parent node and other nodes
const [parentNodeName, ...pathElems]: string[] = node.path.split('/');
// Look-up for the parent node
let parentNode = tree.find(t => t.name === parentNodeName);
// If parent doesn't exist yet, so we create it here
if (!parentNode) {
parentNode = { name: parentNodeName, children: [] }
}
let children = parentNode.children;
// If the level of the node is relevant
// otherwise simply iterate over all pathElems
for(let i = 0; i <= node.level; i ++) {
let child = children.find(c => c.name === pathElems[i]);
// If the child doesn't exist yet - create it
if (!child) {
child = {
name: pathElems[i],
children: []
}
children.push(child);
children = child.children;
continue;
}
// Child does exist, so use it's children for the next iteration
children = child.children;
}
}
return tree;
}
And you can call this function, for example, in ngOnInit:
ngOnInit() {
this.tree = this.transform(this.data);
}
I do not use Angular, but if you just need to convert your flat to nested:
var data = [
{
expandable: true,
level: 0,
name: "2021-12-31",
path: null
},
{
expandable: false,
level: 2,
name: "A.txt",
path: "2021-12-31/B/C/A.txt"
}
]
var to_nested = function(flat) {
var nested = []
var cache = {}
var l = flat.length
var cache_assert = function(name) {
if (cache[name] == null) {
cache[name] = {
name: name,
children: []
}
}
}
for (var i = 0; i < l; i++) {
var current_node = flat[i]
cache_assert(current_node.name)
if (current_node.path == null) {
nested.push(cache[current_node.name])
} else {
var names = current_node.path.split("/")
var parent_name = names.shift()
cache_assert(parent_name)
names.forEach(function(name) {
cache_assert(name)
cache[parent_name].children.push(cache[name])
parent_name = name
})
}
}
return nested
}
var a = to_nested(data)
console.log('a: ', a)
console.log('a: ', a[0].children)
console.log('a: ', a[0].children[0].children)
And if you want to return to flat:
var level = 0
var cache = []; cache[level] = a.slice(0)
var parent = []; parent[level] = null
var index = []; index[level] = 0
while (level >= 0) {
var node = cache[level][index[level]]
if (node != null) {
console.log('node: ', node)
if (
node['children'] != null &&
Object.prototype.toString.call(node['children']) === '[object Array]' &&
node['children'].length
) {
level++
index[level] = 0
parent[level] = Object.assign({}, node)
delete parent[level]['children']
cache[level] = node['children'].slice(0)
} else {
index[level]++
}
} else {
parent[level] = null
level--
index[level]++
}
}
Related
Print elements from object.
Ex.
const emps = {
"Jacobs": ["Emiel", "Svjetlana", "Ivanna"],
"Ivanna": ["Michael", "Lawson"],
"Emiel": ["John", "Ruby"],
"Lawson": [],
"Michael": ["Lindsay", "Ferguson"],
"Ferguson": []
}
In above example lets suppose "Jacob" is Parent of "Emiel", "Svjetlana", "Ivanna", so we have to print sequence "Jacob" "Emiel", "Svjetlana", "Ivanna" means first Parent then childs.
Output should be:
"Jacob"
"Emiel"
"Svjetlana"
"Ivanna"
"Emiel"
"John"
"Ruby"
"Ivanna"
"Michael"
"Lawson"
"Michael"
"Lindsay"
"Ferguson"
const emps = {
"Jacobs": ["Emiel", "Svjetlana", "Ivanna"],
"Ivanna": ["Michael", "Lawson"],
"Emiel": ["John", "Ruby"],
"Lawson": [],
"Michael": ["Lindsay", "Ferguson"],
"Ferguson": []
}
const empsArr = Object.entries(emps)
const empsParents = Object.keys(emps)
const firstChildren = empsArr.map(arr =>
arr[1][0]
)
let leftParents = empsParents.filter(item => item !== empsParents[0]);
const result = empsArr.reduce((acc) => {
const firstChild = acc.firstChild;
// if has children add it and its children
if (emps[firstChild] ?.length > 0) {
leftParents = leftParents.filter(item => item !== firstChild);
return {
res: [...acc.res, firstChild, ...emps[firstChild]],
firstChild: emps[firstChild][0]
}
} else {
// if has no children move on to the item which has not already been added and is not the first child of any item
const newParent = leftParents.find(pName => !firstChildren.includes(pName))
leftParents = leftParents.filter(item => item !== newParent)
if (emps[newParent]) {
const newParentData = emps[newParent].length > 0 ? [newParent, ...emps[newParent]] : []
const firstChildData = typeof emps[firstChild] !== "undefined" ? [firstChild] : []
const newFirstChild = emps[newParent][0];
return {
res: [...acc.res, ...newParentData, ...firstChildData],
firstChild: newFirstChild
}
} else
return acc
}
}, {
res: [
empsParents[0], ...emps[empsParents[0]]
],
firstChild: emps[empsParents[0]][0]
})
result.res.map(item=>console.log(`"${item}"`))
You can use the following code:
const emps = {
"Jacobs": ["Emiel", "Svjetlana", "Ivanna"],
"Ivanna": ["Michael", "Lawson"],
"Emiel": ["John", "Ruby"],
"Lawson": [],
"Michael": ["Lindsay", "Ferguson"],
"Ferguson": []
}
function print(obj) {
for(let x in obj) {
console.log(x);
obj[x].forEach(val => console.log(val));
}
}
print(emps);
Say I have the following strings:
"files/photos/foo.png"
"files/videos/movie.mov"
and I want to convert them to the following object:
{
name: "files"
children: [{
name: "photos",
children: [{
name: "foo.png",
id: "files/photos/foo.png"
}]
},{
name: "videos",
children: [{
name: "movie.mov",
id: "files/videos/movie.mov"
}]
}]
}
What would be the best approach for doing so? I've tried writing some recursive functions, however admit that I'm struggling at the moment.
Here's a quick snippet with a possible solution. It uses nested loops, the outer splitting each path by the delimeter and pop()ing the file portion out of the array. The inner iterates the parts of the path and constructs the heirarchy by reasigning branch on each iteration. Finally the file portion of the path is added to the deepest branch.
const data = [
'files/photos/foo.png',
'files/photos/bar.png',
'files/videos/movie.mov',
'docs/photos/sd.jpg'
];
const tree = { root: {} }
for (const path of data) {
const parts = path.split('/');
const file = parts.pop();
let branch = tree, partPath = '';
for (const part of parts) {
partPath += `${part}/`;
if (partPath === `${part}/`) {
tree.root[partPath] = (tree[partPath] ??= { name: part, children: [] });
} else if (tree[partPath] === undefined) {
tree[partPath] = { name: part, children: [] };
branch.children.push(tree[partPath]);
}
branch = tree[partPath];
}
branch.children.push({ name: file, id: path });
}
const result = Object.values(tree.root)
console.log(JSON.stringify(result, null, 2))
.as-console-wrapper { max-height: 100% !important; top: 0; }
.as-console-row::after { display: none !important; }
Or as a function.
function mergeAssets(assets) {
const tree = { root: {} }
for (const path of data) {
const parts = path.split('/');
const file = parts.pop();
let branch = tree, partPath = '';
for (const part of parts) {
partPath += `${part}/`;
if (partPath === `${part}/`) {
tree.root[partPath] = (tree[partPath] ??= { name: part, children: [] });
} else if (tree[partPath] === undefined) {
tree[partPath] = { name: part, children: [] };
branch.children.push(tree[partPath]);
}
branch = tree[partPath];
}
branch.children.push({ name: file, id: path });
}
return {
name: "assets",
children: Object.values(tree.root)
}
}
const data = [
'files/photos/foo.png',
'files/photos/bar.png',
'files/videos/movie.mov',
'docs/photos/sd.jpg'
];
const result = mergeAssets(data);
console.log(JSON.stringify(result, null, 2))
I was able to find a solution using a recursive function. If others have any tips on how to improve this, I'd love to hear.
function mergeObjects(parentArray,path,originalName){
if(originalName === undefined){
originalName = path;
}
const parts = path.split("/");
var nextPart = "";
parts.forEach((part, index) => index > 0 ? nextPart += (nextPart !== "" ? "/" : "") + part : null);
//does the parentArray contain a child with our name?
const indexOfChild = parentArray.findIndex(child => child.name === parts[0]);
if(indexOfChild === -1){
//this item does not exist
if(parts.length > 1){
var index = parentArray.push({
name: parts[0],
children : []
}) - 1;
mergeObjects(parentArray[index].children,nextPart,originalName);
}else{
parentArray.push({
name: parts[0],
id : originalName
});
}
}else{
//this item already exists
if(parts.length > 1){
mergeObjects(parentArray[indexOfChild].children,nextPart,originalName);
}
}
}
And the function is called with the following:
function mergeAssets(assets){
var obj = {
name: "assets",
children: []
};
assets.forEach(asset => mergeObjects(obj.children,asset));
return obj;
}
var values = [
{ name: 'Car',Data:'Required',value:'Vehicle' },
{ name: 'Auto',Data:'Required',value:'Vehicle'},
{ name: 'Bike',Data:'Required',value:'Vehicle'},
{ name: 'Car',Data:'Required',value:'Vehicle' }
];
Duplicate object will be:
{ name: 'Car',Data:'Required',value:'Vehicle' }
You can check if an array contains the same item on a different index and by that, understand that there is a duplicate:
const values = [
{ name: 'Car',Data:'Required',value:'Vehicle' },
{ name: 'Auto',Data:'Required',value:'Vehicle'},
{ name: 'Bike',Data:'Required',value:'Vehicle'},
{ name: 'Car',Data:'Required',value:'Vehicle' }
];
const duplicates = values.filter((item, idx) => values.findIndex(i => item.name === i.name && item.Data === i.Data && item.value === i.value) !== idx);
console.log(duplicates);
If you want to check all the properties of objects, you have to introduce a new function, and use it to filter your array:
var values = [
{ name: 'Car',Data:'Required',value:'Vehicle' },
{ name: 'Auto',Data:'Required',value:'Vehicle'},
{ name: 'Bike',Data:'Required',value:'Vehicle'},
{ name: 'Bike',Data:'Required',value:'Different VL'},
{ name: 'Car',Data:'Required',value:'Vehicle' }
];
Object.compare = function (obj1, obj2) {
//Loop through properties in object 1
for (var p in obj1) {
//Check property exists on both objects
if (obj1.hasOwnProperty(p) !== obj2.hasOwnProperty(p)) return false;
switch (typeof (obj1[p])) {
//Deep compare objects
case 'object':
if (!Object.compare(obj1[p], obj2[p])) return false;
break;
//Compare function code
case 'function':
if (typeof (obj2[p]) == 'undefined' || (p != 'compare' && obj1[p].toString() != obj2[p].toString())) return false;
break;
//Compare values
default:
if (obj1[p] != obj2[p]) return false;
}
}
//Check object 2 for any extra properties
for (var p in obj2) {
if (typeof (obj1[p]) == 'undefined') return false;
}
return true;
};
var doubles = values.filter((x,i,a) =>a.slice(0,i).find(y=>Object.compare(x,y)))
console.log(doubles)
Working ahead from the answer in:
https://stackoverflow.com/a/840808/11057988
var findDuplicates = (arr) => {
let sorted_arr = arr.slice().sort(sortFunc); // You can define the comparing function here.
// JS by default uses a crappy string compare.
// (we use slice to clone the array so the
// original array won't be modified)
let results = [];
for (let i = 0; i < sorted_arr.length - 1; i++) {
if (sortFunc(sorted_arr[i + 1], sorted_arr[i]) === 0) {
results.push(sorted_arr[i]);
}
}
return results;
}
var values = [
{ name: 'Car',Data:'Required',value:'Vehicle' },
{ name: 'Auto',Data:'Required',value:'Vehicle'},
{ name: 'Bike',Data:'Required',value:'Vehicle'},
{ name: 'Car',Data:'Required',value:'Vehicle' }
];
var sortFunc = (a, b) => {
return (a.name !== b.name) ? a.name.localeCompare(b.name)
: (a.Data !== b.Data) ? a.Data.localeCompare(b.Data)
: a.value.localeCompare(b.value);
};
console.log(`The duplicates are ${JSON.stringify(findDuplicates(values))}`);
// prints The duplicates are [{"name":"Car","Data":"Required","value":"Vehicle"}]
Couple of differences from original answer:
1. The sortFunc is defined as per your data.
2. Since your input is array of objects, the sortFunc is reused for equality check.
I'm trying to print all created groups and they're children so it'll look like that:
[ [ 'Father1', 'Child1', 'Child2', 'Child3' ],
[ 'Father1', 'Child1', 'Child4' ],
[ 'Father1', 'Child1', 'Child5' ] ]
The problems I encountered are varied. from:
var keys = name.keys(o); ^ TypeError: name.keys is not a function to total stack overflow, iv'e debugged the printPath function and it's doing it's job separately but not with my final tree structure.
My tree and print function looks like that:
groups.js:
class groups {
constructor() {
this.root = new Group('root');
}
printPath(name){
this.root.getPath(name)
}
group.js:
class Group {
constructor(name, parent) {
this.name = name;
this.parent = parent || null;
this.children = [];
this.users = new users || null;
}
getPath(name) {
function iter(o, p) {
var keys = name.keys(o);
if (keys.length) {
return keys.forEach(function (k) {
iter(o[k], p.concat(k));
});
}
result.push(p);
}
var result = [];
iter(name, []);
return result;
}
Edit:
For creating a group i'm using a menu handler function:
function createGroup(callback) {
rl.question('Add name for father group: \n', (parent) => {
let parentGroup = programdata.groups.findGroupByName(parent);
if (!parentGroup) {
parentGroup = programdata.groups.root;
}
rl.question('name of new group\n', (groupName) => {
parentGroup.setChildren(new Group(groupName, parentGroup));
console.log(parentGroup);
callback();
});
})
}
findGroupByNameis a nice recursion i made that finds nested groups (feel free to use!) sitting in class groups.
findGroupByName(name) {
if (!name) return null;
return this._findGroupByNameInternal(this.root, name);
}
_findGroupByNameInternal(group, name) {
if (!group) return null;
if (group.name === name) return group;
for (const g of group.children) {
const result = this._findGroupByNameInternal(g, name);
if (!result) continue;
return result;
}
}
And setChildren function placed in class Group:
setChildren(child) {
this.children.push(child);
}
EDIT:
Thank you for the answer, could you please help me realize your method in my menu handler? iv'e tried this: and it giving me nothing.
function createGroup(callback) {
rl.question('Add name for father group: \n', (parent) => {
let parentGroup = programdata.groups.findGroupByName(parent);
let treePath = Group.root.printPath();
if (!parentGroup) {
parentGroup = programdata.groups.root;
}
rl.question('name of new group\n', (groupName) => {
parentGroup.addChild(new Group(groupName, parentGroup));
console.log(treePath);
callback();
});
})
}
The root cause you got the error TypeError: name.keys is not a function is that a string is passed into getPath(name) as argument name, you know the JS string object doesn't have a function property keys.
I refactor your code and fix some error, here is the testable version. Pls put them into the same folder and run test.js.
group.js
class Group {
constructor(name, parent) {
this.name = name;
this.parent = parent || null; // Point to this group's father
this.children = []; // Children of this group, can be sub-group or string
if (!!parent) { // Link to the father
parent.addChild(this);
}
// this.users = new users || null; // Useless, remove it.
}
addChild(...args) {
for(let o in args) {
this.children.push(args[o]);
}
}
/**
* Recursion to build the tree
* #param group
* #returns {*}
*/
iter(group) {
let children = group.children;
if (Array.isArray(children)) { // If the child is a group
if (children.length > 0) {
let result = [];
result.push(group.name);
for (let child of children) {
result.push(group.iter(child));
}
return result;
}
else {
return [];
}
}
else { // If the group is a string
return group;
}
}
getPath() {
return this.iter(this);
}
}
module.exports = Group;
groups.js
let Group = require('./group');
class Groups {
constructor() {
this.root = new Group('root');
}
printPath() {
return this.root.getPath();
}
}
module.exports = Groups;
test.js
let Group = require('./group');
let Groups = require('./groups');
// Root
let rootGroups = new Groups();
// Group 1
let group1 = new Group('Father1', rootGroups.root);
group1.addChild('Child1', 'Child2', 'Child3');
// Group 2
let group2 = new Group('Father1', rootGroups.root);
group2.addChild('Child1', 'Child4');
// Group 3
let group3 = new Group('Father1', rootGroups.root);
group3.addChild('Child1', 'Child5');
let treePath = rootGroups.printPath();
console.log(treePath);
The output is:
[ 'root',
[ 'Father1', 'Child1', 'Child2', 'Child3' ],
[ 'Father1', 'Child1', 'Child4' ],
[ 'Father1', 'Child1', 'Child5' ] ]
Process finished with exit code 0
Enjoy it :)
Ok, found a solution.
Treeshow(){
var node = this.root;
var depth = '-'
recurse( node );
function recurse( node) {
depth +='-'
console.log(depth+node.name);
for (var child in node.children ) {
recurse(node.children[child]);
}
depth = depth.slice(0, -1);
}
}
that will show my tree just like that:
--root
---FooFather
----BarSemiFather
-----FooChild
------BarBaby
I have a javascript object structured like this;
brand: {
group: {
subGroup: {
items: []
},
otherSub: {
items: []
}
}
}
Given an array of keys ['brand', 'group', 'newGroup', 'newSubGroup'] I want to split the keys into found and missing keys. So for the structure above I should get back;
present = ['brand', 'group']
missing = ['newGroup', 'newSubGroup']
I'm using ES6 and have lodash available, but struggling to find a clean way to produce this.
This is not to just check existence, it's recursively find the keys and return those present and the remaining ones.
Here's a pretty sketchy way that works.
const find = (keys, obj) => {
const string = JSON.stringify(obj);
return keys.reduce(({ present, missing }, key) => {
const match = string.match(new RegExp(`"${key}":`));
if (match) {
present.push(key);
} else {
missing.push(key);
}
return { present, missing };
}, { present: [], missing: [] });
}
You can use this function made for you ;)
var getAttrs = function(obj) {
return [].concat.apply([], Object.keys(obj).map(function (key) {
var results = [key]
if (typeof obj[key] === 'object') {
Array.prototype.push.apply(results, getAttrs(obj[key]))
}
return results
}))
}
It return the list of properties and children properties.
getAttrs({brand: {
group: {
subGroup: {
items: []
},
otherSub: {
items: []
}
}
}})
> ["brand", "group", "subGroup", "items", "otherSub", "items"]
And you can use it like so:
var lookingFor = ['brand', 'group', 'newGroup', 'newSubGroup']
var existings = getAttrs(obj)
var missings = []
var presents = []
lookingFor.forEach(attr => {
if (existings.indexOf(attr) === -1) {
missings.push(attr)
} else {
presents.push(attr)
}
})
I wrote a function to recursively get unique keys from a nested object, then filtered the array of all the keys you mentioned checking which were present in the result of my function.
var thisObject = {
brand: {
group: {
subGroup: {
items: []
},
otherSub: {
items: []
}
}
}
};
var arr_full = ['brand', 'group', 'newGroup', 'newSubGroup'] ;
var key_array = [];
function addToKeyArray( key_array, object ){
for( var key in object ){
// only get unique keys
if( key_array.indexOf( key ) === -1 ){
key_array.push( key );
}
// concat the result of calling this function recurrsively on object[key]
key_array.concat( addToKeyArray( key_array, object[key] ) );
}
return key_array;
}
var test = addToKeyArray( [], thisObject );
var missing = arr_full.filter( function( el ) {
return test.indexOf( el ) < 0;
});
console.log( test );
console.log( missing )
You can create recursive function using for...in loop inside another function and return object as result..
var obj = {"brand":{"group":{"subGroup":{"items":[]},"otherSub":{"items":[]}}}}
var keys = ['brand', 'group', 'newGroup', 'newSubGroup'] ;
function findKeys(data, keys) {
keys = keys.slice();
function findPresent(data, keys) {
var result = []
for(var i in data) {
if(typeof data[i] == 'object') result.push(...findPresent(data[i], keys))
var index = keys.indexOf(i);
if(index != -1) result.push(...keys.splice(index, 1))
}
return result
}
return {present: findPresent(data, keys), missing: keys}
}
console.log(findKeys(obj, keys))
To keep things clean and readable you can use "for in", inside a nested function for your recursion.
function recur(obj) {
let preMiss = {
present: [],
missing: []
}
let root = traverse => {
for (let key in traverse) {
if (Array.isArray(traverse[key].items)) {
preMiss.missing.push(key);
}
if (typeof traverse[key] === 'object' && !Array.isArray(traverse[key].items)) {
preMiss.present.push(key);
root(traverse[key])
}
}
}
root(obj);
return preMiss;
}
const object = {
brand: {
group: {
subGroup: {
items: []
},
otherSub: {
items: []
}
}
}
}
console.log(Object.entries(recur(object)));
var toFind = ['brand', 'group', 'newGroup', 'newSubGroup'],
found = [];
var o = {
brand: {
group: {
subGroup: {
items: []
},
otherSub: {
items: []
}
}
}
}
//called with every property and its value
function process(key,value) {
var i = toFind.indexOf(key);
if(i !== -1){
found.push(key);
toFind.splice(i, 1);
}
}
function traverse(o,func) {
if(!toFind.length) return;
for (var i in o) {
func.apply(this,[i,o[i]]);
if (o[i] !== null && typeof(o[i])=="object") {
//going one step down in the object tree!!
traverse(o[i],func);
}
}
}
traverse(o,process);
console.log(found); // present
console.log(toFind); // absent
Traverse method taken from https://stackoverflow.com/a/722732/1335165
Even though this question is a bit older, I want to present a rather short solution to the problem.
const recursivelyGetKeys = obj => Object.keys(obj).map(key => typeof obj[key] === 'object'
? [...recursivelyGetKeys(obj[key]), key] : [key]).reduce((p, c) => [...p, ...c], [])
This function will return all keys in the object, so a call to the array arr with
const arr = {
brand: {
group: {
subGroup: {
items: []
},
otherSub: {
items: []
}
}
}
}
will output:
const keys = recursivelyGetKeys(arr) // = ["items", "subGroup", "items", "otherSub", "group", "brand"]
Now to find the intersection set of this and find = ['brand', 'group', 'newGroup', 'newSubGroup'], do:
const found = keys.filter(key => find.some(val === key))
const missing = keys.filter(key => find.every(val !== key))