How to optimise this aggregation using Array.reduce - javascript

I have attached a working snippet ,I am looking for more optimised solution ,as I will be dealing with fairly large amount of data .Any solution with fairly less complexity and more optimised which you can suggest thanks.
let data = [
{
category_name: "Home",
isparent: true
},
{
category_name: "City",
isparent: true
},
{
category_name: "Home town",
parent: "Home",
isparent: false
},
{
category_name: "City_town",
parent: "City",
isparent: false
}
]
/**
* req
* let data=[
* {category_name:"home",
* children:[{""}]
* }
* ]
*/
let res = data.reduce((acc, ele) => {
let obj = {
children: []
}
if (ele.isparent) {
obj = ele;
obj.children = [];
acc.push(obj);
} else {
let _filt = acc.find(_ele => _ele.category_name == ele.parent)
_filt.children.push(ele);
}
return acc;
}, [])
console.log(JSON.stringify(res));

Your current solution is inefficient because of the nested acc.find() - it will cause the solution to approach O(N^2). You can achieve a much more efficient solution as you have unique keys, just make a map/dictionary (native js object works fine for this) - I think it is a much easier format to work with but you can simply Object.values(map) to get the requested output format.
let data = [ { category_name: "Home", isparent: true }, { category_name: "City", isparent: true }, { category_name: "Home town", parent: "Home", isparent: false }, { category_name: "City_town", parent: "City", isparent: false } ];
let res = Object.values(data.reduce((acc, ele) => {
if (ele.isparent) {
acc[ele.category_name] = ele;
ele.children = [];
} else {
acc[ele.parent].children.push(ele);
}
return acc;
}, {}));
console.log(JSON.stringify(res));
This implementation only requires one loop over the data and is closer to O(N).

Would something like this work? Not sure if it's more performant, but maybe a little?
const as_obj = data.reduce((accum, curr) => {
const { category_name, isparent, parent } = curr;
if (isparent && accum[category_name]) {
// This is a parent, but we've already seen one of its children;
// we created the parent key for that already.
return accum;
}
if (isparent && !accum[category_name]) {
// This is the first time we've seen the parent key, creating an object for it.
return { ...accum, [category_name]: { ...curr, children: [] } };
}
if (parent && !accum[parent]) {
// We haven't seen this child's parent yet, but we'll make a place for it.
accum[parent] = { category_name, children: [] };
}
const children = [ ...accum[parent].children, curr];
return { ...accum, [parent]: { ...accum.parent, children } };
}, {});
const res = Object.values(as_obj);

Related

Turn array of delimited keys into nested JSON structure [duplicate]

Let's assume I have the following array:
[
"About.vue",
"Categories/Index.vue",
"Categories/Demo.vue",
"Categories/Flavors.vue"
]
We use the Index.vue in each sub-folder to act as the parent of that folder. That means the above would look like:
[
{
name: "About",
children: []
},
{
name: "Categories",
children:
[
{
name: "Index.vue",
children: []
},
{
name: "Demo.vue",
children: []
},
{
name: "Flavors.vue",
children: []
}
]
}
]
I was able to get it working slightly by using the following tutorial: https://joelgriffith.net/array-reduce-is-pretty-neat/
However, the thing about that is that it is a root object with a property for each file, as opposed to an array with an object for each file.
The following code produces the intended output:
let paths = [
"About.vue",
"Categories/Index.vue",
"Categories/Demo.vue",
"Categories/Flavors.vue"
];
let helper = {
index: -1,
name: ""
};
function treeify(files) {
var fileTree = [];
function mergePathsIntoFileTree(prevDir, currDir, i, filePath) {
helper.name = currDir;
helper.index = i;
if (helper.index == 0) {
let index = prevDir.findIndex(x => x.name == helper.name);
if (index < 0) {
prevDir.push({
name: helper.name,
children: []
});
}
return prevDir;
}
if (helper.index >= 0) {
let obj = {
name: currDir,
children: []
};
prevDir[helper.index].children.push(obj);
helper.index = i;
helper.name = currDir;
}
}
function parseFilePath(filePath) {
var fileLocation = filePath.split('/');
// If file is in root directory, eg 'index.js'
if (fileLocation.length === 1) {
fileTree[0] = {
name: fileLocation[0],
children: []
};
} else {
fileLocation.reduce(mergePathsIntoFileTree, fileTree);
}
}
files.forEach(parseFilePath);
return fileTree;
}
console.log(treeify(paths));
However, it fails on the following input:
let paths = [
"About.vue",
"Categories/Index.vue",
"Categories/Demo.vue",
"Categories/Flavors.vue",
"Categories/Types/Index.vue",
"Categories/Types/Other.vue"
];
Does anyone know a solution to get it working for further nested lists of paths?
You can create this structure using forEach method to loop each path and split it to array on /, then you can also use reduce method to create nested objects.
let paths = ["About.vue","Categories/Index.vue","Categories/Demo.vue","Categories/Flavors.vue","Categories/Types/Index.vue","Categories/Types/Other.vue"];
let result = [];
let level = {result};
paths.forEach(path => {
path.split('/').reduce((r, name, i, a) => {
if(!r[name]) {
r[name] = {result: []};
r.result.push({name, children: r[name].result})
}
return r[name];
}, level)
})
console.log(result)
So, first off, I am going to assume this is in Node.js, second, I am currently at home so I don't have access to node.js at the moment so I had no real way of testing the code, however the following code should work.
What you need to do is check the contents of the folder and then make a check to see if an item in the folder is a directory or not, if true, call the function again with the new path (a.k.a. recursion).
So first you start by reading the folder, add each item's name to the .name property of the object, then you check if it's a folder or not, if it is, recursive for that path. Keep returning an array of objects back (this will be added to the .children property.
var fs = require('fs');
var filetree = DirToObjectArray('path/to/folder/');
function DirToObjectArray(path) {
var arr = [];
var content = fs.readdirSync(path, { withFileTypes: true });
for (var i=0; i< content.length; i++) {
var obj = new Object({
name: "",
children: []
});
obj.name = content[i].name;
if (content[i].isDirectory()) {
obj.children = DirToObjectArray(path + content[i].name + "/");
}
arr.push(obj);
}
return arr;
}
If you are not using node.js but in-browser javascript, I can't help you with that
You could take an iterative approach for every found name part and get an object and return the children for the next search.
var paths = ["About.vue", "Categories/Index.vue", "Categories/Demo.vue", "Categories/Flavors.vue", "Categories/Types/Index.vue", "Categories/Types/Other.vue"],
result = paths.reduce((r, p) => {
var names = p.split('/');
names.reduce((q, name) => {
var temp = q.find(o => o.name === name);
if (!temp) q.push(temp = { name, children: [] });
return temp.children;
}, r);
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I went with #Nenad Vracar's answer (and upvoted, thank you!), but I also had the need to allow duplicate filenames in my use case. I just wanted to share how I did that.
let paths = ["About.vue","Categories/Index.vue","Categories/Demo.vue","Categories/Flavors.vue","Categories/Types/Index.vue","Categories/Types/Other.vue","Categories/Types/Other.vue","Categories/Types/Other.vue"];
let result = [];
let level = {result};
paths.forEach(path => {
path.split('/').reduce((r, name, i, a) => {
if(!r[name]) {
r[name] = {result: []};
r.result.push({name, children: r[name].result});
} else if (i === a.length - 1) {
// Allow duplicate filenames.
// Filenames should always be at the end of the array.
r.result.push({name, children: []});
}
return r[name];
}, level)
})
console.log(result)
The following solution was derived from #nenad-vracar's answer. One shortcoming with his answer is that if a path contains "result", the code will fail. A simple workaround would be to rename "result" to "", that is, include characters that cannot appear in a path.
export interface IPathNode {
name: string;
children: IPathNode[];
path: IPath | null;
}
export interface IPath {
key: string;
directory: boolean;
}
interface IPathLevel {
// ["<result>"]: IPathNode[];
[key: string]: IPathLevel | IPathNode[];
}
export const createPathTree = (paths: IPath[]): IPathNode | null => {
const level: IPathLevel = { ["<result>"]: [] as IPathNode[] };
paths.forEach((path) => {
path.key.split("/").reduce(
((
currentLevel: IPathLevel,
name: string,
index: number,
array: string[]
) => {
if (!currentLevel[name]) {
currentLevel[name] = { ["<result>"]: [] };
(currentLevel["<result>"] as IPathNode[]).push({
name,
children: (currentLevel[name] as IPathLevel)[
"<result>"
] as IPathNode[],
/* Attach the path object to the leaf node. */
path: index === array.length - 1 ? path : null,
});
}
return currentLevel[name];
}) as any,
level
);
});
const finalArray = level["<result>"] as IPathNode[];
return finalArray.length > 0 ? finalArray[0] : null;
};
console.log(
JSON.stringify(
createPathTree([
{
key: "/components/button.tsx",
directory: false,
},
{
key: "/components/checkbox.tsx",
directory: false,
},
{
key: "/result",
directory: true,
},
]),
null,
4
)
);
Output:
{
"name": "",
"children": [
{
"name": "components",
"children": [
{
"name": "button.tsx",
"children": [],
"path": {
"key": "/components/button.tsx",
"directory": false
}
},
{
"name": "checkbox.tsx",
"children": [],
"path": {
"key": "/components/checkbox.tsx",
"directory": false
}
}
],
"path": null
},
{
"name": "result",
"children": [],
"path": {
"key": "/result",
"directory": true
}
}
],
"path": null
}

In typescript the filtering the array is not working

filtervalue = {
serviceLine:['cca','ga']
}
this.inProgresDetailsData = [{serviceLine:'cca'}, { serviceLine:'cca'}, { serviceLine:'bca'}]
this.hrResourceDetailsdata= this.inProgresDetailsData.filter(item => {
for (let index = 0; index < filterValue.serviceLine.length; index++) {
item.serviceLine == filterValue.serviceLine[index]
}
});`
this.hrResourceDetailsdata is empty on filtering
The Array.prototype.filter (documentation) function expects you to return a boolean value that decides whether to keep the current item or not.
Since you are originally not returning any value, you implicitly return undefined which when checked against a boolean resolves to false, hence, all of your array items are discarded one by one, leaving none remaining.
You want to return the boolean to make this work:
this.hrResourceDetailsdata = this.inProgresDetailsData.filter(item => {
for (let index = 0; index < filterValue.serviceLine.length; index++) {
if (item.serviceLine == filterValue.serviceLine[index]) {
return true; // return true if found, otherwise continue
}
}
return false; // filter is never found in loop, fall back to false
});
Also, you can use Array.prototype.includes (documentation) to simplify your check, the following code does the exact same thing:
this.hrResourceDetailsdata = this.inProgresDetailsData.filter(item => {
// return true if `item.serviceLine` is inside `filterValue.serviceLine`
return filterValue.serviceLine.includes(item.serviceLine);
});
You can create a Set out of filterValue.serviceLine and filter out the items that are present in the set.
const
filterValue = { serviceLine: ["cca", "ga"] },
inProgressDetailsData = [
{ serviceLine: "cca" },
{ serviceLine: "cca" },
{ serviceLine: "bca" },
],
filterSet = new Set(filterValue.serviceLine),
hrResourceDetailsdata = inProgressDetailsData.filter(({ serviceLine }) =>
filterSet.has(serviceLine)
);
console.log(hrResourceDetailsdata);
If you want to apply multiple filters, then:
Create an object containing filters in form of Sets.
Filter items where all the filters are applicable using Array.prototype.every.
const
filterValue = {
serviceLine: ["cca", "ga"],
urgency: ["medium", "high"],
},
filterSet = Object.fromEntries(
Object.entries(filterValue).map(([k, v]) => [k, new Set(v)])
),
inProgressDetailsData = [
{ id: 1, urgency: "low", serviceLine: "cca" },
{ id: 2, urgency: "medium", serviceLine: "cca" },
{ id: 3, urgency: "low", serviceLine: "bca" },
{ id: 4, urgency: "high", serviceLine: "ga" },
{ id: 5, urgency: "low", serviceLine: "abc" },
],
hrResourceDetailsdata = inProgressDetailsData.filter((data) =>
Object.entries(filterSet).every(([k, v]) => v.has(data[k]))
);
console.log(hrResourceDetailsdata);
Code
filtervalue = {
serviceLine:['cca','ga']
};
this.inProgresDetailsData = [
{ serviceLine:'cca'},
{ serviceLine:'cca'},
{ serviceLine:'bca'}
];
this.hrResourceDetailsdata = this.inProgresDetailsData.filter(item => {
return filtervalue.serviceLine.includes(item.serviceLine)
});
console.log(this.hrResourceDetailsdata)

Create a tree from a list of strings containing paths of files - javascript

Let's assume I have the following array:
[
"About.vue",
"Categories/Index.vue",
"Categories/Demo.vue",
"Categories/Flavors.vue"
]
We use the Index.vue in each sub-folder to act as the parent of that folder. That means the above would look like:
[
{
name: "About",
children: []
},
{
name: "Categories",
children:
[
{
name: "Index.vue",
children: []
},
{
name: "Demo.vue",
children: []
},
{
name: "Flavors.vue",
children: []
}
]
}
]
I was able to get it working slightly by using the following tutorial: https://joelgriffith.net/array-reduce-is-pretty-neat/
However, the thing about that is that it is a root object with a property for each file, as opposed to an array with an object for each file.
The following code produces the intended output:
let paths = [
"About.vue",
"Categories/Index.vue",
"Categories/Demo.vue",
"Categories/Flavors.vue"
];
let helper = {
index: -1,
name: ""
};
function treeify(files) {
var fileTree = [];
function mergePathsIntoFileTree(prevDir, currDir, i, filePath) {
helper.name = currDir;
helper.index = i;
if (helper.index == 0) {
let index = prevDir.findIndex(x => x.name == helper.name);
if (index < 0) {
prevDir.push({
name: helper.name,
children: []
});
}
return prevDir;
}
if (helper.index >= 0) {
let obj = {
name: currDir,
children: []
};
prevDir[helper.index].children.push(obj);
helper.index = i;
helper.name = currDir;
}
}
function parseFilePath(filePath) {
var fileLocation = filePath.split('/');
// If file is in root directory, eg 'index.js'
if (fileLocation.length === 1) {
fileTree[0] = {
name: fileLocation[0],
children: []
};
} else {
fileLocation.reduce(mergePathsIntoFileTree, fileTree);
}
}
files.forEach(parseFilePath);
return fileTree;
}
console.log(treeify(paths));
However, it fails on the following input:
let paths = [
"About.vue",
"Categories/Index.vue",
"Categories/Demo.vue",
"Categories/Flavors.vue",
"Categories/Types/Index.vue",
"Categories/Types/Other.vue"
];
Does anyone know a solution to get it working for further nested lists of paths?
You can create this structure using forEach method to loop each path and split it to array on /, then you can also use reduce method to create nested objects.
let paths = ["About.vue","Categories/Index.vue","Categories/Demo.vue","Categories/Flavors.vue","Categories/Types/Index.vue","Categories/Types/Other.vue"];
let result = [];
let level = {result};
paths.forEach(path => {
path.split('/').reduce((r, name, i, a) => {
if(!r[name]) {
r[name] = {result: []};
r.result.push({name, children: r[name].result})
}
return r[name];
}, level)
})
console.log(result)
So, first off, I am going to assume this is in Node.js, second, I am currently at home so I don't have access to node.js at the moment so I had no real way of testing the code, however the following code should work.
What you need to do is check the contents of the folder and then make a check to see if an item in the folder is a directory or not, if true, call the function again with the new path (a.k.a. recursion).
So first you start by reading the folder, add each item's name to the .name property of the object, then you check if it's a folder or not, if it is, recursive for that path. Keep returning an array of objects back (this will be added to the .children property.
var fs = require('fs');
var filetree = DirToObjectArray('path/to/folder/');
function DirToObjectArray(path) {
var arr = [];
var content = fs.readdirSync(path, { withFileTypes: true });
for (var i=0; i< content.length; i++) {
var obj = new Object({
name: "",
children: []
});
obj.name = content[i].name;
if (content[i].isDirectory()) {
obj.children = DirToObjectArray(path + content[i].name + "/");
}
arr.push(obj);
}
return arr;
}
If you are not using node.js but in-browser javascript, I can't help you with that
You could take an iterative approach for every found name part and get an object and return the children for the next search.
var paths = ["About.vue", "Categories/Index.vue", "Categories/Demo.vue", "Categories/Flavors.vue", "Categories/Types/Index.vue", "Categories/Types/Other.vue"],
result = paths.reduce((r, p) => {
var names = p.split('/');
names.reduce((q, name) => {
var temp = q.find(o => o.name === name);
if (!temp) q.push(temp = { name, children: [] });
return temp.children;
}, r);
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I went with #Nenad Vracar's answer (and upvoted, thank you!), but I also had the need to allow duplicate filenames in my use case. I just wanted to share how I did that.
let paths = ["About.vue","Categories/Index.vue","Categories/Demo.vue","Categories/Flavors.vue","Categories/Types/Index.vue","Categories/Types/Other.vue","Categories/Types/Other.vue","Categories/Types/Other.vue"];
let result = [];
let level = {result};
paths.forEach(path => {
path.split('/').reduce((r, name, i, a) => {
if(!r[name]) {
r[name] = {result: []};
r.result.push({name, children: r[name].result});
} else if (i === a.length - 1) {
// Allow duplicate filenames.
// Filenames should always be at the end of the array.
r.result.push({name, children: []});
}
return r[name];
}, level)
})
console.log(result)
The following solution was derived from #nenad-vracar's answer. One shortcoming with his answer is that if a path contains "result", the code will fail. A simple workaround would be to rename "result" to "", that is, include characters that cannot appear in a path.
export interface IPathNode {
name: string;
children: IPathNode[];
path: IPath | null;
}
export interface IPath {
key: string;
directory: boolean;
}
interface IPathLevel {
// ["<result>"]: IPathNode[];
[key: string]: IPathLevel | IPathNode[];
}
export const createPathTree = (paths: IPath[]): IPathNode | null => {
const level: IPathLevel = { ["<result>"]: [] as IPathNode[] };
paths.forEach((path) => {
path.key.split("/").reduce(
((
currentLevel: IPathLevel,
name: string,
index: number,
array: string[]
) => {
if (!currentLevel[name]) {
currentLevel[name] = { ["<result>"]: [] };
(currentLevel["<result>"] as IPathNode[]).push({
name,
children: (currentLevel[name] as IPathLevel)[
"<result>"
] as IPathNode[],
/* Attach the path object to the leaf node. */
path: index === array.length - 1 ? path : null,
});
}
return currentLevel[name];
}) as any,
level
);
});
const finalArray = level["<result>"] as IPathNode[];
return finalArray.length > 0 ? finalArray[0] : null;
};
console.log(
JSON.stringify(
createPathTree([
{
key: "/components/button.tsx",
directory: false,
},
{
key: "/components/checkbox.tsx",
directory: false,
},
{
key: "/result",
directory: true,
},
]),
null,
4
)
);
Output:
{
"name": "",
"children": [
{
"name": "components",
"children": [
{
"name": "button.tsx",
"children": [],
"path": {
"key": "/components/button.tsx",
"directory": false
}
},
{
"name": "checkbox.tsx",
"children": [],
"path": {
"key": "/components/checkbox.tsx",
"directory": false
}
}
],
"path": null
},
{
"name": "result",
"children": [],
"path": {
"key": "/result",
"directory": true
}
}
],
"path": null
}
My answer is inspired from #Nenad Vracar. But unlike his solution where he used for each and reduce which I think unnecessary.
let final = {result:[]};
for (const path of paths) {
let context = final;
for (const name of path.split('/')) {
if (!context[name]) {
context[name] = {result:[]};
context.result.push({name, children: context[name].result});
}
context = context[name];
}
}
console.log(final.result)

compare two Object array and match value

I have two Object of array like following
var a={
firstObj:
[
{
Ref: "5ca3cd6aefbc9f1782b5db53",
status: "hhhh"
},
{
Ref: "5ca3cdc6efbc9f1782b5db5c",
status: "hhhh"
},
{
Ref: "5ca3cdc6efbc9f1782b5db5c",
status: "hhhh"
},
{
Ref: "5ca3cdc6efbc9f1782b5db5c",
status: "hhhh"
}
]
};
var b={
secondObj: [
{
_id: "5ca3cd6aefbc9f1782b5db53"
},
{
_id: "5ca3cdc6efbc9f1782b5db5c"
},
]
}
I want to check if a.firstObj has matching Ref to b.secondObj._id if it has then I am trying to assign into firstObj element to matching b.secondObj.new but somehow _id is not matching
I am trying through map
a.firstObj.map(item =>
b.secondObj.map((_item, index) => {
console.log(_item._id.toString());
console.log(item.Ref.toString());
if (_item._id == item.Ref) {
b.secondObj[index].new = item;
}
})
);
AFAI tested... yes it's matching. Take a look:
var a={
firstObj:
[
{
Ref: "5ca3cd6aefbc9f1782b5db53",
status: "hhhh"
},
{
Ref: "5ca3cdc6efbc9f1782b5db5c",
status: "hhhh"
},
{
Ref: "5ca3cdc6efbc9f1782b5db5c",
status: "hhhh"
},
{
Ref: "5ca3cdc6efbc9f1782b5db5c",
status: "hhhh"
}
]
};
var b={
secondObj: [
{
_id: "5ca3cd6aefbc9f1782b5db53"
},
{
_id: "5ca3cdc6efbc9f1782b5db5c"
},
]
}
a.firstObj.map(item =>
b.secondObj.map((_item, index) => {
//console.log(_item._id.toString());
//console.log(item.Ref.toString());
if (_item._id == item.Ref) {
b.secondObj[index].new = item;
}
})
);
console.log(b)
You probably missed something. If there's anything else you want to know, please reformulate and I'll change the answer.
Please understand that with this code, as a object has many equal Ref elements, the association will be with the last Ref.
You can do a map with an inner reduce:
var a={
firstObj:
[
{
Ref: "5ca3cd6aefbc9f1782b5db53",
status: "hhhh"
},
{
Ref: "5ca3cdc6efbc9f1782b5db5c",
status: "hhhh"
},
{
Ref: "5ca3cdc6efbc9f1782b5db5c",
status: "hhhh"
},
{
Ref: "5ca3cdc6efbc9f1782b5db5c",
status: "hhhh"
}
]
};
var b={
secondObj: [
{
_id: "5ca3cd6aefbc9f1782b5db53"
},
{
_id: "5ca3cdc6efbc9f1782b5db5c"
},
]
}
function mergeByEqProp (propA, propB, listA, listB) {
return listB.map(function (b) {
return listA.reduce(function (acc, a) {
if (!acc && a[propA] === b[propB]) {
return Object.assign({}, b, {new: a});
}
return acc;
}, null);
});
}
console.log(mergeByEqProp('Ref', '_id', a.firstObj, b.secondObj));
It first maps over the second list and then reduces the items in the first list until it finds a match. As soon as it finds a match, it always returns that. I added that functionality because it looks like there is some repetition in a.firstObj (have a close look at the Ref properties). In case that's fine, just change the if (!acc && a[propA] === b[propB]) { part to if (a[propA] === b[propB]) {.
The Object.assign part deserves a bit of explanation as well:
First of all, in case you don't work in an ES6/ES2015+ environment, you need another function as a replacement – $.extend for example works well. Or write your own, like:
function assign (a /*, ...rest */) {
var r = Array.prototype.slice.call(arguments, 1);
return r.reduce(function (acc, x) {
for (var y in x) {
if (x.hasOwnProperty(y)) { acc[y] = x[y]; }
}
return acc;
}, a);
}
The other thing to explain is the empty {}: It is merely there to create a copy first and alter that copy instead of the original item. If that doesn't bother you, just use Object.assign(b, a) (or your own assign(b, a) respectively).
/* This code will identify all the items that match with Ref id and then assign them into new property inside the secondObj as an array */
<script type="text/javascript">
var a={
firstObj:
[
{
Ref: "5ca3cd6aefbc9f1782b5db53",
status: "abcd"
},
{
Ref: "5ca3cdc6efbc9f1782b5db5c",
status: "efgh"
},
{
Ref: "5ca3cdc6efbc9f1782b5db5c",
status: "hijk"
},
{
Ref: "5ca3cdc6efbc9f1782b5db5c",
status: "lmno"
}
]
};
var b={
secondObj: [
{
_id: "5ca3cd6aefbc9f1782b5db53"
},
{
_id: "5ca3cdc6efbc9f1782b5db5c"
},
]
}
let equality = [];
for(let i=0; i<b.secondObj.length ; i++){
let itemB = b.secondObj[i];
equality.push(a.firstObj.filter(itemA => itemA.Ref == itemB._id)) ;
}
equality.map((value, index) => b.secondObj[index].new = value); //<=== in case if there are more than one equal items in firstObj
//equality.map((value, index) => b.secondObj[index].new = value[0]); <== only asign one item to 'new'
console.log(b);
</script>

Tree from an associative array

There is an array:
let docs = [
{ "_id":"1", parent:"_", "title":"one"},
{ "_id":"2", parent:"1", "title":"two"},
{ "_id":"4", parent:"_", "title":"title"},
{ "_id":"5", parent:"4", "title":"www"},
{"_id":"_", "name":"root" },
];
I need to get out of it that's a tree:
{'_id':'_','name':'root','child':
[
{'_id':'1','parent':'_','title':'one','child':
[
{'_id':'2','parent':'1','title':'two','child':[]}
]
},
{'_id':'4','parent':'_','title':'title','child':
[
{'_id':'6','parent':'4','title':'vvv','child':[]}
]
}
]
}
But my code only works if the parent element is always higher on the list than the children, and I want to make that work universally.
This is code:
let node = {};
for (let doc of docs) {
doc.child = [];
node[doc._id] = doc;
if (typeof doc.parent === "undefined")
tree = doc;
else
node[doc.parent].child.push(doc);
}
console.log('tree->', JSON.stringify(tree));
code on codepen:
http://codepen.io/alex183/pen/OWvrPG?editors=0112
You can create recursive function using reduce method and basically check in each iteration of the parent property of current object is equal to passed parent param in function call.
let docs = [
{ "_id":"1", parent:"_", "title":"one"},
{ "_id":"2", parent:"1", "title":"two"},
{ "_id":"4", parent:"_", "title":"title"},
{ "_id":"5", parent:"4", "title":"www"},
{"_id":"_", "name":"root" }
];
function makeTree(data, parent = undefined) {
return data.reduce((r, e) => {
// check if current e.parent is equal to parent
if (e.parent === parent) {
// make a copy of current e so we keep original as is
const o = { ...e }
// set value as output of recursive call to child prop
o.child = makeTree(data, e._id)
// push to accumulator
r.push(o)
}
return r
}, [])
}
console.log(makeTree(docs))
This is a proposal with Array#reduce and Map. It sorts the array in advance.
var docs = [{ _id: "1", parent: "_", title: "one" }, { _id: "2", parent: "1", title: "two" }, { _id: "4", parent: "_", title: "title" }, { _id: "5", parent: "4", title: "www" }, { _id: "_", name: "root" }],
order = { undefined: -2, _: -1 },
tree = docs
.sort((a, b) => (order[a.parent] || a.parent) - (order[b.parent] || b.parent) || a._id - b._id)
.reduce(
(m, a) => (
m
.get(a.parent)
.push(Object.assign({}, a, { child: m.set(a._id, []).get(a._id) })),
m
),
new Map([[undefined, []]])
)
.get(undefined);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
The quick and dirty way is to use a sort function.
docs = docs.sort((a, b) => (a._id - b._id));

Categories