Transform array to object tree [JS] - javascript

People!
It's my first question here as junior frontend dev.
I have function (https://jsfiddle.net/kmjhsbt9/) which transforms a flat array like this :
const filePaths = [
'src/lib/git.js',
'src/lib/server.js',
'build/css/app.css',
'externs/test/jquery.js',
];
into object tree like this:
[
src: {
lib: {
git.js: 'file',
server.js: 'file',
}
},
build: {
css: {
app.css: 'file'
}
}
.....
]
Please, help me to understand how I can rewrite the function so that it outputs the result in this format:
[
{
text: src,
children: [
{
text: 'lib',
children: [
{
text: git.js,
children: [
{
text: 'file'
},
]
},
{
text: server.js,
children: [
{
text: 'file'
},
]
}
]
}
]
},
{
text: build,
children: [
{
text: app.css,
children: [
text: app.css,
children: [
{
text: 'file'
}
]
]
}
]
}
.....
]
function:
const getTree = (arr) => {
let fileTree = [];
function mergePathsIntoFileTree(prevDir, currDir, i, filePath) {
if (!prevDir.hasOwnProperty(currDir)) {
prevDir[currDir] = {};
}
if (i === filePath.length - 1) {
prevDir[currDir] = 'file';
}
return prevDir[currDir];
}
function parseFilePath(filePath) {
let fileLocation = filePath.split('/');
if (fileLocation.length === 1) {
return (fileTree[fileLocation[0]] = 'file');
}
fileLocation.reduce(mergePathsIntoFileTree, fileTree);
}
arr.forEach(parseFilePath);
return fileTree;
}
Thank you very much, in advance!

You can achieve this by recursively iterating over the keys of the output object (the one that gets generated by getTree):
const filePaths = [
'src/lib/git.js',
'src/lib/server.js',
'build/css/app.css',
'externs/test/jquery.js',
];
// building the dependecy tree,
//an alternative version to your "getTree" function, but a bit cleaner
function getTree(filePaths) {
return filePaths.reduce((all, item) => {
let pointer = null;
item.split('/').forEach((el, i, arr) => {
if (!i) pointer = all;
if (!pointer.hasOwnProperty(el)) {
pointer[el] = (i === arr.length - 1) ? 'file' : {};
}
pointer = pointer[el];
});
return all;
}, {});
}
// formatting the dependency tree to match the desired output with recursion
function buildChildTree(obj) {
return Object.keys(obj).map(key => {
return {
text: key,
children: obj[key] === 'file' ? [{text:'file'}] : buildChildTree(obj[key])
}
});
}
const dependencyTree = getTree(filePaths);
const result = buildChildTree(dependencyTree);
console.log(JSON.stringify(result, null, 2));

Related

How to fix the count variable value in a recursive method using react and javascript?

I have data like below:
const arr_obj = [
{
id: '1',
children: [],
type: 'TYPE1',
},
{
id: '2',
children: [
{
id: '1',
children: [
{
//some attributes
},
],
type: 'MAIN',
},
{
id: '2',
children: [
{
//some attributes
},
],
type: 'MAIN',
},
{
id: '3',
children: [
{
//some attributes
},
],
type: 'MAIN',
},
],
type: 'TYPE2',
},
{
id: '3',
children: [
{
id: '4',
children: [
{
//some attributes
},
],
type: 'MAIN',
},
{
id: '5',
children: [
{
//some attributes
},
],
type: 'MAIN',
},
{
id: '6',
children: [
{
//some attributes
},
],
type: 'MAIN',
},
],
type: 'TYPE2',
},
];
I have to find out the count of type: 'MAIN'. these 'MAIN' will be within type: 'TYPE2'
So the expected count is 6.
below is the code,
const ParentComponent = () => {
const findCount = (arr_obj) => {
let count = 0;
const expectedCount = 2;
const loop = (children) => {
for (const obj of children) {
const { type, children } = obj;
if (type === 'TYPE2') {
loop(children);
} else if (type === 'MAIN') {
++count;
if (count > expectedCount) return;
}
}
};
loop(children);
return count > expectedCount;
};
const output = findCount(arr_obj);
return (
//some jsx rendering
);
};
The above code works fine, but I want to make a loop (children) function a pure function. I am not sure how to do it.
The problem now is: I define variables outside the loop method.
How can I define everything as arguments to the function? You could move the function outside the component.
I have tried something like below to make it a pure function
const loop = (children, count = 0) => {
if (!children) return;
for (const obj of children) {
const { type, children } = obj;
if (type === 'TYPE2') {
loop(children, count + 1);
} else if (type === 'MAIN') {
++count;
if (count > expectedCount) return;
}
}
console.log('count', count); //this is 0 always when i log
return count;
};
const ParentComponent = () => {
const output = React.useMemo(() => {
return loop(arr_obj);
}, [arr_obj]);
console.log('output', output); // output is always false
return (
//some jsx rendering
)
};
Now the problem with above code is that the count is always 0. I am not sure where the problem is.
Your approach is fine: you update count by passing it as a parameter and by returning its updated value. However, the function returns at three spots and you only return count at the third spot. Furthermore, you call loop recursively, but there, you don't use its return value and you should pass count instead of count + 1 as an argument.
You need to make the following changes:
Inside loop, replace return; with return count; two times.
Inside loop, replace loop(children, count + 1); with count = loop(children, count);
Now, if you would remove if (count > expectedCount) return;, you would get 6 as the result.
I'm not sure what exactly you want but I don't think you'd need to make it complicated with recursion. You can simply achieve the same function like this:
const findCount = (arr) => {
const filteredArray = arr
.filter(el => el.type === 'TYPE2')
// removed all elements whose type is not TYPE2
.map(el => el.children)
// replaced all element with element's children array
.flat()
// flattened the array
.filter(el => el.type === 'MAIN');
// removed all elements whose type is not MAIN
return filteredArray.length;
};

JavaScript Array attribute change

I have an array like this.
let arr = [
{
"ABBRIVATION":"ISB",
"name":"ISLAMABAD",
},
{
"ABBRIVATION":"RAW",
"name":"PINDI",
},
{
"ABBRIVATION":"SWB",
"name":"SWABI",
},
{
"ABBRIVATION":"AQ",
"name":"AQEEL",
},
]
I want to change it to like this
let me explain it a little. I want to assign the abbreviation directly to the name and the iterate through that array
let outout = [
{
"ISB":"ISLAMABAD"
},
{
"RAW":"ISLAMABAD"
},
{
"SWB":"SWABI"
},
{
"AQ":"AQEEL"
},
]
that is what I tried
let k = arr.map((item) => {
return item.ABB = item.name
})
console.log(k)
and here is the output
[ 'ISLAMABAD', 'PINDI', 'SWABI', 'AQEEL' ]
Here you go, use array map, simples
let arr = [
{
"ABBRIVATION":"ISB",
"name":"ISLAMABAD",
},
{
"ABBRIVATION":"RAW",
"name":"PINDI",
},
{
"ABBRIVATION":"SWB",
"name":"SWABI",
},
{
"ABBRIVATION":"AQ",
"name":"AQEEL",
},
]
let outout = arr.map(({ABBRIVATION, name}) => ({[ABBRIVATION]: name}));
console.log(outout);
Nothing more than a simple Array.prototype.map() needed.
let arr = [
{
ABBRIVATION: "ISB",
name: "ISLAMABAD",
},
{
ABBRIVATION: "RAW",
name: "PINDI",
},
{
ABBRIVATION: "SWB",
name: "SWABI",
},
{
ABBRIVATION: "AQ",
name: "AQEEL",
},
];
const result = arr.map(e => ({ [e.ABBRIVATION]: e.name }));
console.log(result);
map over the array of objects (map returns a new array) and assign the name to a new key defined by the abbreviation.
You code works the way it does because item.ABB is undefined, but you're also assigning item.name to it which does get returned, so you just get an array of names returned.
const arr=[{ABBRIVATION:"ISB",name:"ISLAMABAD"},{ABBRIVATION:"RAW",name:"PINDI"},{ABBRIVATION:"SWB",name:"SWABI"},{ABBRIVATION:"AQ",name:"AQEEL"}];
const out = arr.map(obj => {
return { [obj.ABBRIVATION]: obj.name };
});
console.log(out);
Hi I have seen people answer, but most of them use the map function, I provide some other solutions, hoping to expand the thinking
Use forEach function
const datas = [
{
"ABBRIVATION":"ISB",
"name":"ISLAMABAD",
},
{
"ABBRIVATION":"RAW",
"name":"PINDI",
},
{
"ABBRIVATION":"SWB",
"name":"SWABI",
},
{
"ABBRIVATION":"AQ",
"name":"AQEEL",
}
];
datas.forEach((obj, i, arr) => {
const{'ABBRIVATION':k, 'name':v} = obj;
arr[i] = {[k]:v};
});
console.log(datas);
Use flatMap function
const datas = [
{
"ABBRIVATION":"ISB",
"name":"ISLAMABAD",
},
{
"ABBRIVATION":"RAW",
"name":"PINDI",
},
{
"ABBRIVATION":"SWB",
"name":"SWABI",
},
{
"ABBRIVATION":"AQ",
"name":"AQEEL",
}
];
const result = datas.flatMap(obj => {
const {'ABBRIVATION':k, 'name':v} = obj;
return {[k]:v};
});
console.log(result);
this is how you suppose to do it.
arr.reduce((d, c)=>([...d, {[c.ABBRIVATION]: c.name}]),[])
let arr = [
{
"ABBRIVATION":"ISB",
"name":"ISLAMABAD",
},
{
"ABBRIVATION":"RAW",
"name":"PINDI",
},
{
"ABBRIVATION":"SWB",
"name":"SWABI",
},
{
"ABBRIVATION":"AQ",
"name":"AQEEL",
},
]
console.log(arr.reduce((data, current)=>([...data, {[current.ABBRIVATION]: current.name}]),[]))

convert a flat array that has a "path" property to a nested array

I have a flat array like this example :
[
{
'name':'itemA',
'path':'foo/bar'
},
{
'name':'itemB',
'path':'bar/foo'
},
{
'name':'itemC',
'path':'foo'
},
{
'name':'itemD',
'path':'bar'
},
{
'name':'itemE',
'path':'foo/bar/wizz'
},
{
'name':'itemF',
'path':'bar/foo'
},
]
I want to build a tree based on the "path" property, so I could get this output :
[
{
'name':'itemD',
'path':'bar',
'items':[
{
'name':'itemD',
'path':'bar/foo'
},
{
'name':'itemF',
'path':'bar/foo'
}
]
},
{
'name':'itemC',
'path':'foo',
'items':[
{
'name':'itemA',
'path':'foo/bar',
'items':
[
{
'name':'itemE',
'path':'foo/bar/wizz'
}
]
},
]
}
]
How could I achieve that ?
I found out some examples like this one, but they are based on a parent ID and not a "path" like mine.
Thanks a lot !
You could find the level or add a new object for the level of the splitted path.
const
data = [{ name: 'itemA', path: 'foo/bar' }, { name: 'itemB', path: 'bar/foo' }, { name: 'itemC', path: 'foo' }, { name: 'itemD', path: 'bar' }, { name: 'itemE', path: 'foo/bar/wizz' }, { name: 'itemF', path: 'bar/foo' }],
tree = data.reduce((items, { name, path }) => {
path.split('/').reduce((o, _, i, p) => {
let path = p.slice(0, i + 1).join('/'),
temp = (o.items ??= []).find(q => q.path === path);
if (!temp) o.items.push(temp = { name, path });
return temp;
}, { items });
return items;
}, []);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Merge array of arrays based sub array index as keys (NodeJS/Javascript)

How to write a code that would merge my list in the following way? Performance is important. I want to convert the following array:
"list": [
[
"marketing",
"page_sections",
"PageOne"
],
[
"marketing",
"page_sections",
"PageTwo"
],
[
"webapp",
"page",
"pageone"
],
[
"webapp",
"page",
"pagetwo"
],
To the following format:
[
{
name: "marketing",
path: "marketing/",
children: [
{
name: "page_sections",
path: "marketing/page_sections",
children: [
{
name: "pageOne",
path: "marketing/page_sections/pageOne",
children: []
},
{
name: "pageTwo",
path: "marketing/page_sections/pageTwo",
children: []
},
}
],
},
{
name: "webapp",
path: "webapp/"
children: [
{
name: "page",
path: "webapp/page/"
children: [
{
name: "pageone",
path: "webapp/page/pageone"
children: []
},
{
name: "pagetwo",
path: "webapp/page/pagetwo"
children: []
},
}
]
},
]
The first index of sub array is parent, second index is child of parent, third index is child of second index (and so on).
The shortest approach is to iterate the nested names and look for an object with the same name. If not exist, create a new object. Return the children array as new level.
This approach features Array#reduce for iterating the outer array of data and for all inner arrays.
const
data = [["marketing", "page_sections", "PageOne"], ["marketing", "page_sections", "PageTwo"], ["webapp", "page", "pageone"], ["webapp", "page", "pagetwo"]],
result = data.reduce((r, names) => {
names.reduce((level, name, i, values) => {
let temp = level.find(q => q.name === name),
path = values.slice(0, i + 1).join('/') + (i ? '' : '/');
if (!temp) level.push(temp = { name, path, children: [] });
return temp.children;
}, r);
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Looking at the source, and your expected result.
What I would do is loop the list, and then do another loop inside the list. Mix this with Array.find..
Eg..
const data = {list:[
["marketing","page_sections","PageOne"],
["marketing","page_sections","PageTwo"],
["webapp","page","pageone"],
["webapp","page","pagetwo"]]};
function makeTree(src) {
const root = [];
for (const s of src) {
let r = root;
let path = '';
for (const name of s) {
path += `${name}/`;
let f = r.find(k => k.name === name);
if (!f) r.push(f = {name, path, children: []});
r = f.children;
}
}
return root;
}
console.log(makeTree(data.list));
.as-console-wrapper {
min-height: 100%;
}
You can do the following,
list= [
[
"marketing",
"page_sections",
"PageOne"
],
[
"marketing",
"page_sections",
"PageTwo"
],
[
"webapp",
"page",
"pageone"
],
[
"webapp",
"page",
"pagetwo"
],
];
getChildrenItem = (arr) => {
if(arr.length === 1) {
return { name: arr[0], children: []};
} else {
return { name: arr.splice(0,1)[0], children: [getChildrenItem([...arr])]};
}
}
merge = (srcArr, newObj) => {
const {name, children} = newObj;
let index = srcArr.findIndex(item => item.name === name);
if( index> -1) {
children.forEach(item => merge(srcArr[index].children, item))
return ;
} else {
srcArr.push(newObj);
return;
}
}
allObj = [];
list.forEach(item => {
let tempObj = getChildrenItem([...item]);
merge(allObj, tempObj);
});
console.log(allObj);
If Performance is an issue, I think this is one of the best solutions.
let list = [
["marketing", "page_sections", "PageOne"],
["marketing", "page_sections", "PageTwo"],
["webapp", "page", "pageone"],
["webapp", "page", "pagetwo"],
];
const dt = {};
const pushToOBJ = (Object, name) => {
if (Object[name]) return Object[name];
Object[name] = {
name,
children: {},
};
return Object[name];
};
for (let i = 0; i < list.length; i++) {
let subArray = list[i];
let st = pushToOBJ(dt, subArray[0]);
for (let j = 1; j < subArray.length; j++) {
st = pushToOBJ(st.children, subArray[j]);
}
}
let result = [];
const convertObjToChildArray = (obj) => {
if (obj === {}) return [];
let arr = Object.values(obj);
for (let i = 0; i < arr.length; i++) {
arr[i].children = convertObjToChildArray(arr[i].children);
}
return arr;
};
result = convertObjToChildArray(dt);
console.log(result);
No Use of JS find function, which already has O(n) Complexity.

Converting an array of objects (with string array elements) into a nested tree

I've been trying to solve a problem with arrays and objects for some time now - but I still can't find the right solution.
This is my starting position:
list = [
{
lastItemData: {data:"root"},
path: ["Root"]
},
{
lastItemData: {data:"level2_Summer"},
path: ["Root", "Level2_Summer"]
},
{
lastItemData: {data:"Level3_Monday"},
path: ["Root", "Level2_Winter", "Level3_Monday"]
},
{
lastItemData: {data:"Level4_Morning"},
path: ["Root", "Level2_Spring", "Level3_Tuesday", "Level4_Morning"]
},
{
lastItemData: {data:"Level3_Sunday"},
path: ["Root", "Level2_Autumn", "Level3_Sunday"]
}]
and this is what i need:
result = [
{
text: "Root",
lastItemData: {data:"root"},
Items:[
{
text:"Level2_Summer",
lastItemData: {data:"level2_Summer"},
Items: []
},
{
text:"Level2_Winter",
Items:[
{
text: "Level3_Monday",
lastItemData: {data:"Level3_Monday"},
Items: []
}
]
},
{
text:"Level2_Spring",
Items:[
{
text: "Level3_Tuesday"
Items: [
{
text:"Level4_Morning"
Items:[],
lastItemData: {data:"Level4_Morning"},
}
]
}
]
},
{
text:"Level2_Autumn",
Items:[
{
text: "Level3_Sunday"
}
]
},
]
}]
I'v try something like this (code is based on user jonas answer from my deleted post)
const property = list.reduce((previous, current, index) => {
let acc = {}; //the accumulator that will go deep into the object
const result = acc; //our result will be the top level object
debugger;
//Now we iterate over our array
for (var i = 0; i < current.stringPath.length; i++) {
debugger;
//add the string part
acc["level" + (i + 1)] = current.stringPath[i];
//Add the children array
acc["Items"] = [{}];
//And go into the object
acc = acc["Items"][0];
}
console.log(result)
previous.push(result);
return previous;
}, []);
console.log("property", property);
But unfortunately the result does not correspond to the desired structure. Can anyone help me?
This is a far more complex problem as it looks like, because you have to build a nested structure with a given name (text property) and you need to check if that name exist or not. If not create a new object and push it to the parent array.
This solution features a hash table where all nested hashes for each level are collected and maintained. The result is an array with the wanted tree.
It works as follows:
First have a look into the root of the hash table and check if the path value exists. If not, create a new node with the information and push it to the hash collector, denoted with _. Then return that node and go on with iteration of the given path.
At the and of the path use lastItemData for the wanted object.
var list = [{ lastItemData: { data: "root" }, path: ["Root"] }, { lastItemData: { data: "level2_Summer" }, path: ["Root", "Level2_Summer"] }, { lastItemData: { data: "Level3_Monday" }, path: ["Root", "Level2_Winter", "Level3_Monday"] }, { lastItemData: { data: "Level4_Morning" }, path: ["Root", "Level2_Spring", "Level3_Tuesday", "Level4_Morning"] }, { lastItemData: { data: "Level3_Sunday" }, path: ["Root", "Level2_Autumn", "Level3_Sunday"] }],
tree = function (array) {
var result = [],
hash = { _: { Items: result } };
array.forEach(function (object) {
object.path.reduce(function (o, p) {
if (!o[p]) {
o[p] = { _: { text: p, Items: [] } };
o._.Items.push(o[p]._);
}
return o[p];
}, hash)._.lastItemData = object.lastItemData;
});
return result;
}(list);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories

Resources