Merge array of arrays based sub array index as keys (NodeJS/Javascript) - 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.

Related

How to iterate children from array in javascript?

i Have an array that could be :
arr1 = ["node1","children1","children1.1","children1.1.1"]
or it could be
arr2 = ["node1","children1"]
and I want to make it in this json format :
const data_arr1 = [{
title: "Node 1",
childNodes: [
{ title: "Childnode 1" ,
childNodes: [
{
title: "Childnode 1.1",
childNodes: [
{ title: "Childnode 1.1.1" }
]
}
]
}
]
}];
var data_arr2 = {title:"node1",childNodes:{title:"children1"}}
I have do like that but i can't have the right format in iterative way :
BuildJson = (items) => {
const elements = items.split(",");
let result = {};
var children = []
result["title"] = elements[0];
elements.shift()
if(elements.length>1) {
for(var i=0;i<elements.length;i++){
elements.map((el,idx)=> {
children.push({title:el})
})
}
result["ChildNodes"] = children
}
Please how can I fix this algorithm ?
I suggest you to use recursive function.
I made you an example:
const t = ["lvl1", "lvl2", "lvl3"];
const r = (array) => {
if (array.length === 1) {
return {
title: array[0]
};
}
if (array.length > 1) {
return {
title: array[0],
childNode: r(array.slice(1))
};
}
};
r(t);
r(t) returned the following JSON:
{
"title": "lvl1",
"childNode": {
"title": "lvl2",
"childNode": {
"title": "lvl3"
}
}
}
const arr1 = ["node1","children1","children1.1","children1.1.1"]
const createArray = (arr, i = 0) => {
const obj = {
title: arr[i]
};
if (i < arr.length - 1) {
obj.childNodes = createArray(arr, ++i);
}
return [obj];
}
const newArr = createArray(arr1);
console.log(newArr);

Relate and merge array of same Department

I am working on an application where I need to get combine the object of same department based on the
conditions provided in the second Array and attach the relation to the object.
let inArr1 = [{"D1D2":"AND"},{"D3D4":"OR"}]
let inArr2 =[{"ID":"1","NAME":"KEN","DEPT1":"CSE"},
{"ID":"2","NAME":"MARK","DEPT2":"IT"},
{"ID":"3","NAME":"TOM","DEPT3":"ECE"},
{"ID":"4","NAME":"SHIV","DEPT4":"LIB"},
{"ID":"5","NAME":"TIM","DEPT5":"SEC"}
]
Output
outArr ={
[{"ID":"1","NAME":"KEN","DEPT1":"CSE","REL":"AND"},
{"ID":"2","NAME":"MARK","DEPT2":"IT","REL":"AND"}], //Arr1
[{"ID":"3","NAME":"TOM","DEPT3":"ECE","REL":"OR"},
{"ID":"4","NAME":"SHIV","DEPT4":"LIB","REL":"OR"}], //Arr2
[{"ID":"5","NAME":"TIM","DEPT5":"SEC"}] //Arr3
}
Code:
let condArr=[],outArr,i=1;
inArr1.forEach(condt => {
let dept = Object.keys(condt)[0];
let tmparr = dept.split("D");
tmparr.shift()
condArr.push(tmparr)
});
inArr2.forEach(condt => {
if(condArr.includes(inArr2.D+i)){
i++;
outArr.push(inArr2);
}
});
Your code has a bit confused logic, i would suggest rather this
let inArr1 = [{"D1D2":"AND"},{"D3D4":"OR"},{"D5D6":"AND"}]
let inArr2 =[{"ID":"1","NAME":"KEN","DEPT1":"CSE"},
{"ID":"2","NAME":"MARK","DEPT2":"IT"},
{"ID":"3","NAME":"TOM","DEPT3":"ECE"},
{"ID":"4","NAME":"SHIV","DEPT4":"LIB"},
{"ID":"5","NAME":"TIM","DEPT5":"SEC"},
{"ID":"6","NAME":"TLA","DEPT6":"SEC"},
]
// first lets create object of ids as keys and conditions as values
const [keys, conditions] = inArr1.reduce((agg, cond, index) => {
Object.entries(cond).forEach(([key, value]) => {
key.split('D').forEach(v => { if (v) agg[0][v] = { value, index }})
agg[1].push([])
})
return agg
}, [{}, []]) // {1: "AND", 2: "AND", 3: "OR", 4: "OR"}
conditions.push([])
// and now just map over all elements and add condition if we found id from the keys
inArr2.forEach(item => {
const cond = keys[item.ID]
if (cond) conditions[cond.index].push({...item, REL: cond.value})
else conditions[conditions.length - 1].push(item)
})
const res = conditions.filter(v => v.length)
console.log(res)
You could store the goups by using the ID and use new objects.
let inArr1 = [{ D1D2: "AND" }, { D3D4: "OR" }],
inArr2 = [{ ID: "1", NAME: "KEN", DEPT1: "CSE" }, { ID: "2", NAME: "MARK", DEPT2: "IT" }, { ID: "3", NAME: "TOM", DEPT3: "ECE" }, { ID: "4", NAME: "SHIV", DEPT4: "LIB" }, { ID: "5", NAME: "TIM", DEPT5: "SEC" }],
groups = inArr1.reduce((r, o) => {
Object.entries(o).forEach(([k, REL]) => {
var object = { REL, group: [] };
k.match(/[^D]+/g).forEach(id => r[id] = object);
});
return r;
}, {}),
grouped = inArr2.reduce((r, o) => {
var { REL, group } = groups[o.ID] || {};
if (group) {
if (!group.length) r.push(group);
group.push(Object.assign({}, o, { REL }));
} else {
r.push([o]);
}
return r;
}, []);
console.log(grouped);
.as-console-wrapper { max-height: 100% !important; top: 0; }
can try other solution:
let inArr1 = [{ D1D2: "AND" }, { D3D4: "OR" }, { D6D7: "XOR" }];
let inArr2 = [
{ ID: "1", NAME: "KEN", DEPT1: "CSE" },
{ ID: "2", NAME: "MARK", DEPT2: "IT" },
{ ID: "3", NAME: "TOM", DEPT3: "ECE" },
{ ID: "4", NAME: "SHIV", DEPT4: "LIB" },
{ ID: "5", NAME: "TIM", DEPT5: "SEC" },
{ ID: "9", NAME: "BAR", DEPT5: "XYZ" },
{ ID: "6", NAME: "FOO", DEPT5: "XYZ" },
];
let unmatchedArr = []
let matchedArr = inArr2.reduce((acc, obj) => {
// getting index matched from inArr1 objects key
const indexMatched = getIndexMatch(obj.ID);
// creating index if not exists
if (!acc[indexMatched] && indexMatched !== null) acc[indexMatched] = [];
// if some index matched it merge current obj with DEL property with inArr1[indexMatched] key => value
return indexMatched !== null
? acc[indexMatched].push({
...obj,
DEL: inArr1[indexMatched][Object.keys(inArr1[indexMatched])[0]]
})
// pushing on unmatchedArr
: unmatchedArr.push(obj)
, acc
}, []);
function getIndexMatch(id) {
for (const [index, obj] of inArr1.entries()) {
for (const key of Object.keys(obj)) {
// spliting only digits of the current key of object
if (key.match(/\d/g).includes(id)) return index; // returning index of inArr1 if is included
}
}
return null;
}
// merging arrays
const result = [...matchedArr, unmatchedArr];
console.log(result);

JavaScript: How to convert nested array of object to key-value pair objects

It can be duplicate question but i have tried a lot but i did not get expected result.Could some one help me.
I am getting an array in request body like :
[
{
"name":"array",
"book":[
{
"name":"name1",
"book":"book1"
},
{
"name":"name2",
"book":"book2"
}
]
},
{
"name":"name3",
"book":"book3"
}
]
And I need to convert the array of nested array to below format
{
array: [
{
name1: "book1"
},
{
name2: "book2"
}
],
name3: "book3"
}
Note:In some cases book can be array or string.
On my first attempt i have tried below code to convert it into single object but it doest not convert nested array to key value pair
const array=[
{
"name":"array",
"book":[
{
"name":"name1",
"book":"book1"
},
{
"name":"name2",
"book":"book2"
}
]
},
{
"name":"name3",
"book":"book3"
}
]
var result = {};
for (var i = 0; i < array.length; i++) {
result[array[i].name] = array[i].value;
}
console.log(result);
Response for the above code
{
array: [
{
name: "name1",
book: "book1"
},
{
name: "name2",
book: "book2"
}
],
name3: "book3"
}
EDITED
I have made little change in the Code from the Ahmed's answer and it worked
const res=[
{
"name":"array",
"book":[
{
"name":"name1",
"book":"book1"
},
{
"name":"name2",
"book":"book2"
}
]
},
{
"name":"name3",
"book":"book3"
}
]
const obj = {}
for(let i = 0 ; i < res.length; i++){
let name = res[i].name
if(Array.isArray(res[i]['book'])){
obj[name] = [];
for(let item in res[i]['book']){
let key = res[i]['book'][item]['name']
let value = res[i]['book'][item]['book']
let entry = {}
entry[key] = value
obj[name].push(entry)
}
}
else{
obj[res[i].name]=res[i].book;
}
}
console.log(obj);
The added snippet should solve your problem.
The problem in your code was Appropriate nesting you didn't access the wanted values and didn't handle all the cases Hence, The wrong output.
const res = [
{
"name":"array",
"book":[
{
"name":"name1",
"book":"book1"
},
{
"name":"name2",
"book":"book2"
}
]
},
{
"name":"name3",
"book":"book3"
}
]
const obj = {}
for(let i = 0 ; i < res.length; i++){
let name = res[i].name
if(Array.isArray(res[i]['book'])){
obj[name] = []
for(let item in res[i]['book']){
let key = res[i]['book'][item]['name']
let value = res[i]['book'][item]['book']
let entry = {}
entry[key] = value
obj[name].push(entry)
}
}
else{
obj[name] = res[i]['book']
}
}
for(let item in obj){
console.log(item)
console.log(obj[item])
}

Convert paths with items to tree object

I'm trying to convert an array of object contains paths with item to tree of data so I wrote a function path loop on the path:
From this array:
[
{ userName: "1", tags: ["A;B"] },
{ userName: "2", tags: ["A;B"] },
{ userName: "3", tags: ["A;"] },
{ userName: "4", tags: ["A;B;C"] },
{ userName: "5", tags: ["A;B"] },
{ userName: "6", tags: ["A;B;C;D"] }
]
to this structure:
[{
name: "A",
families: [{
name: "B",
families: [{
name: "C",
families: [{
name: "D",
families: [],
items: ["6"]
}],
items: ["4"]
}],
items: ["1", "2", "5"]
}],
items: ["3"]
}]
function convertListToTree(associationList) {
let tree = [];
for (let i = 0; i < associationList.length; i++) {
let path = associationList[i].tags[0].split(';');
let assetName = associationList[i].userName;
let currentLevel = tree;
for (let j = 0; j < path.length; j++) {
let familyName = path[j];
let existingPath = findWhere(currentLevel, 'name', familyName);
if (existingPath) {
if (j === path.length - 1) {
existingPath.items.push(assetName);
}
currentLevel = existingPath.families;
} else {
let assets = [];
if (j === path.length - 1) {
assets.push(assetName)
}
let newPart = {
name: familyName,
families: [],
items: assets,
};
currentLevel.push(newPart);
currentLevel = newPart.families;
}
}
}
return tree;
}
function findWhere(array, key, value) {
let t = 0;
while (t < array.length && array[t][key] !== value) {
t++;
}
if (t < array.length) {
return array[t]
} else {
return false;
}
}
But I have some issue here that the expected output is not like I want
[
{
"name": "A",
"families": [
{
"name": "B",
"families": [
{
"name": "C",
"families": [
{
"name": "D",
"families": [],
"items": [
"6"
]
}
],
"items": [
"4"
]
}
],
"items": [
"1",
"2",
"5"
]
},
{
"name": "",
"families": [],
"items": [
"3"
]
}
],
"items": []
}
]
Can someone please help me to fix that
You should be able to use recursion to achieve this, using getFamilies and getUsers functions called at each level:
const allTags = ["A", "B", "C", "D"];
let a = [ { "userName": "1", "tags": ["A;B"] }, { "userName": "2", "tags": ["A;B"] }, { "userName": "3", "tags": ["A;"] }, { "userName": "4", "tags": ["A;B;C"] }, { "userName": "5", "tags": ["A;B"] }, { "userName": "6", "tags": ["A;B;C;D"] } ];
// This function assumes order is not important, if it is, remove the sort() calls.
function arraysEqual(a1, a2) {
return a1.length === a2.length && a1.sort().every(function(value, index) { return value === a2.sort()[index]});
}
function getUserNames(tags, arr) {
return arr.filter(v => arraysEqual(v.tags[0].split(';').filter(a => a),tags)).map(({userName}) => userName);
}
function getFamilies(tags) {
if (tags.length >= allTags.length) return [];
const name = allTags[tags.length];
const path = [...tags, name];
return [{ name, families: getFamilies(path), items: getUserNames(path, a)}];
}
let res = getFamilies([]);
console.log('Result:', JSON.stringify(res, null, 4));
The idea here is to iterate the data (the reduce loop), and whenever a node is missing from the Map (nodesMap), use createBranch to recursively create the node, create the parent (if needed...), and then assign the node to the parent, and so on. The last step is to get a unique list of root paths (A in your data), and extract them from the Map (tree) to an array.
const createBranch = ([name, ...tagsList], nodesMap, node) => {
if(!nodesMap.has(name)) { // create node if not in the Map
const node = { name, families: [], items: [] };
nodesMap.set(name, node);
// if not root of branch create the parent...
if(tagsList.length) createBranch(tagsList, nodesMap, node);
};
// if a parent assign the child to the parent's families
if(node) nodesMap.get(name).families.push(node);
};
const createTree = data => {
const tree = data.reduce((nodesMap, { userName: item, tags: [tags] }) => {
const tagsList = tags.match(/[^;]+/g).reverse(); // get all nodes in branch and reverse
const name = tagsList[0]; // get the leaf
if(!nodesMap.has(name)) createBranch(tagsList, nodesMap); // if the leaf doesn't exist create the entire branch
nodesMap.get(name).items.push(item); // assign the item to the leaf's items
return nodesMap;
}, new Map());
// get a list of uniqnue roots
const roots = [...new Set(data.map(({ tags: [tags] }) => tags.split(';')[0]))];
return roots.map(root => tree.get(root)); // get an array of root nodes
}
const data = [{"userName":"1","tags":["A;B"]},{"userName":"2","tags":["A;B"]},{"userName":"3","tags":["A;"]},{"userName":"4","tags":["A;B;C"]},{"userName":"5","tags":["A;B"]},{"userName":"6","tags":["A;B;C;D"]}];
const result = createTree(data);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Allow me to make two small changes, and ramda's mergeDeepWithKey will do most of the work for you.
Changes, before we start:
Make tags an array rather than an array containing one string (i.e. tags[0].split(";"))
Allow families to be a dictionary-like object rather than an array (if you ever need your array format, it's Object.values(dict))
Solution:
Transform every entry to a path of the desired format using reduce
Merge all paths with custom logic:
When merging name entries, don't change the name
When merging items entries, concatenate
const inp = [
{ userName: "1", tags: ["A","B"] },
{ userName: "2", tags: ["A","B"] },
{ userName: "3", tags: ["A"] },
{ userName: "4", tags: ["A","B","C"] },
{ userName: "5", tags: ["A","B"] },
{ userName: "6", tags: ["A","B","C","D"] }
];
// Transform an input element to a nested path of the right format
const Path = ({ userName, tags }) => tags
.slice(0, -1)
.reduceRight(
(families, name) => ({ name, families: { [families.name]: families },
items: []
}),
({ name: last(tags), families: {}, items: [userName] })
);
// When merging path entries, use this custom logic
const mergePathEntry = (k, v1, v2) =>
k === "name" ? v1 :
k === "items" ? v1.concat(v2) :
null;
const result = inp
.map(Path)
// Watch out for inp.length < 2
.reduce(
mergeDeepWithKey(mergePathEntry)
)
console.log(JSON.stringify(result, null, 2));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const { mergeDeepWithKey, last } = R;</script>

Transform array to object tree [JS]

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));

Categories

Resources