Related
Assume this below array of objects that sorted by code property in ascii order:
var codes = [
{ code: '01' },
{ code: '0101' },
{ code: '0102' },
{ code: '010201' },
{ code: '0103' },
{ code: '02' },
{ code: '0201' },
{ code: '0202' },
];
How can I convert this to a nested array like this :
var nestedCodes = [
{
code: '01',
children: [
{ code: '0101' },
{
code: '0102',
children: [
{ code: '010201' }
]
},
{ code: '0103' }
]
},
{
code: '02',
children: [
{ code: '0201' },
{ code: '0202' }
]
}
];
The structure of codes is like concatenating multiple 0N that N can be a number between 1 and 9. Note that codes come from server and there would be some additional properties beside code like title but it doesn't matter in this problem.
The main idea here is to make an appropriate format for jsTree.
You can do this with a recursive solution. The idea is to maintain the path (obtained as an array via String.prototype.match with a regex) and the parent under which you want to insert the code for each recursive call.
The parent keeps track of the node you want to pick in the "current" recursive call, and path helps in building the parent as you keep going deeper:
function insert(d, path, parent, arr) {
if (path.length === 0) {
arr.push(Object.assign({}, d));
return;
}
var target = arr.find(e => e.code === parent);
target.children = target.children || [];
insert(d, path.slice(1), parent + path[0], target.children);
}
var codes = [
{ code: '01' },
{ code: '0101' },
{ code: '0102' },
{ code: '010201' },
{ code: '0103' },
{ code: '02' },
{ code: '0201' },
{ code: '0202' },
];
var res = codes.reduce((a, c) => {
var p = c.code.match(/(0[1-9])/g);
insert(c, p.slice(1), p[0], a);
return a;
}, []);
console.log(res);
The assumption, of course, is that when a code is being inserted, its parent has already been inserted before.
I struggled quite a bit to write the recursive function that will build the required structure. Found the answer here
But to do that, you must first add parent property to each of your codes array.
I did that on the assumption that each code has a parent that is equivalent to the code itself except for the last two bytes.
var codes = [{code: '01' },
{code: '0101' },
{code: '0102' },
{code: '010201'},
{code: '0103' },
{code: '02' },
{code: '0201' },
{code: '0202' },
];
// add parents to each code
codes.forEach(function(c) {
if (c.code.length > 2) {
c.parent = c.code.substr(0, c.code.length - 2);
} else {
c.parent = 'root';
}
});
function find_children(arr, parent) {
var out = [];
for (var i in arr) {
if (arr[i].parent == parent) {
var children = find_children(arr, arr[i].code);
if (children.length) {
arr[i].children = children;
}
out.push(arr[i])
}
}
return out;
}
var nested = find_children(codes,'root');
console.log(nested);
The code is a little long but pretty easy to understand in my opinion. It's extremely robust - does not require the array to be sorted and doesn't require 01 to exist to process 0102 (in case it's needed). The code can be much shorter without handling these cases, but I thought you might be need this.
Firstly, create a object-based tree data structure out of the data. This tree has keys and values, and is very efficient to build because accessing by index is O(1). Next, convert the object-based tree into the final array-based tree data structure by traversing the object-based tree and then converting each layer into arrays.
I also make heavy use of recursion since recursion is well suited for creating and traversing trees.
Compared to the other answers, my algorithm has better time complexity because I create a dictionary/object which has O(1) access when creating the tree. The other algorithms do a search within each layer, which is inefficient. My algorithm runs in O(N) whereas the other answers here are shorter but run in O(N^2).
Just copy the format function into your code and it should be good to use.
const codes = [
{ code: '01' },
{ code: '0101' },
{ code: '0102' },
{ code: '010201' },
{ code: '0103' },
{ code: '02' },
{ code: '0201' },
{ code: '0202' },
];
function format(codes) {
// Splits the string into an array of 2-character strings.
const SPLIT_REGEX = /.{2}(?=(.{2})+(?!.))|.{2}$/g;
const codeFragments = codes.map(obj => obj.code.match(SPLIT_REGEX));
// 1. Represent the data as a tree which is more efficient to build.
const tree = {};
function createTree(tree, fragments) {
let node = tree;
fragments.forEach(fragment => {
if (!node[fragment]) {
node[fragment] = {};
}
node = node[fragment];
});
}
codeFragments.forEach(fragments => createTree(tree, fragments));
/* tree will have the structure:
{
"01": {
"01": {},
"02": {
"01": {}
},
"03": {}
},
"02": {
"01": {},
"02": {}
}
}
*/
// 2. Convert the tree structure into the desired format.
function generateCodesFromTree(tree, previous) {
const nestedCodes = [];
Object.keys(tree).forEach(treeNode => {
const code = previous + treeNode;
const children = generateCodesFromTree(tree[treeNode], code);
const nestedCode = { code };
if (children.length > 0) {
nestedCode.children = children;
}
nestedCodes.push(nestedCode);
});
return nestedCodes;
}
return generateCodesFromTree(tree, '');
}
console.log(format(codes));
It can only be achieved by using a recursive approach. Try this one.
let codes = [
{ code: '01' },
{ code: '0101' },
{ code: '0102' },
{ code: '010201' },
{ code: '0103' },
{ code: '02' },
{ code: '0201' },
{ code: '0202' },
];
roots = codes.filter(c => c.code.length === 2);
roots.forEach(c => assign(c));
console.log(roots);
function assign(code) {
codes.forEach(c => {
if (c !== code) {
if (code.code === c.code.slice(0, code.code.length)) {
code.children = !code.children ? [c] : [...code.children, c];
assign(code.children[code.children.length - 1]);
}
}
});
}
I have a list:
var list = ['parent-element', 'child-of-previus-element-1', 'child-of-previus-element-2'];
where each next element in the array is a child of the previous.
I want to transform this list into a tree structure, e.g.:
{
"parent-element": {
"childrens": [{
"child-of-previus-element-1": {
"childrens": [{
"child-of-previus-element-2": {
"childrens": []
}
}]
}
}]
}
}
I have tried:
var list = ['parent-element', 'child-of-previus-element-1', 'child-of-previus-element-2'];
var tree = {};
for (var i = 0; i < list.length; i++) {
if( list[i-1] && tree[list[i-1]] ){
tree[list[i-1]].childrens[list[i]] = {"childrens": []};
} else {
tree[list[i]] = {
"childrens": []
};
}
}
console.log( JSON.stringify(tree) );
but the output is:
{
"parent-element":{
"childrens":[]
},
"child-of-previus-element-2":{
"childrens":[]
}
}
You might reduce it right:
var list = ['parent-element', 'child-of-previus-element-1', 'child-of-previus-element-2'];
var tree = list.reduceRight((child, key) => ({ [key]: { children: [child] } }), null);
console.log(JSON.stringify(tree))
Given the following array:
[
{
"real":104.1530776708426,
"workHour":8,
"value":null
},
{
"real":71.53948769310401,
"workHour":9
},
{
"real":97.84076993321577,
"workHour":10
},
{
"real":115.72564185649178,
"workHour":11
},
{
"real":79.95589800993977,
"workHour":12
},
{
"real":91.52846219558896,
"workHour":13
},
{
"real":57.86282092824589,
"workHour":14
},
{
"real":148.33923183423036,
"workHour":15
},
{
"real":125.19410346293202,
"workHour":16
},
{
"real":67.33128253468612,
"workHour":17
},
{
"real":55.75871834903695,
"workHour":18
},
{
"real":102.04897509163365,
"workHour":19
},
{
"real":132.55846249016332,
"workHour":20
},
{
"real":138.87077022779013,
"workHour":21
},
{
"real":60,
"workHour":8
},
{
"real":52,
"workHour":9
},
{
"real":114,
"workHour":10
},
{
"real":115,
"workHour":11
},
{
"real":92,
"workHour":12
},
{
"real":102,
"workHour":13
},
{
"real":54,
"workHour":14
},
{
"real":62,
"workHour":15
},
{
"real":133,
"workHour":16
},
{
"real":116,
"workHour":17
},
{
"real":106,
"workHour":18
},
{
"real":115,
"workHour":19
},
{
"real":115,
"workHour":20
},
{
"real":125,
"workHour":21
}
]
How can I find where the workHour match, and combine real there?
I did it with pure JS
const array = [{"real":104.1530776708426,"workHour":8,"value":null},{"real":71.53948769310401,"workHour":9},{"real":97.84076993321577,"workHour":10},{"real":115.72564185649178,"workHour":11},{"real":79.95589800993977,"workHour":12},{"real":91.52846219558896,"workHour":13},{"real":57.86282092824589,"workHour":14},{"real":148.33923183423036,"workHour":15},{"real":125.19410346293202,"workHour":16},{"real":67.33128253468612,"workHour":17},{"real":55.75871834903695,"workHour":18},{"real":102.04897509163365,"workHour":19},{"real":132.55846249016332,"workHour":20},{"real":138.87077022779013,"workHour":21},{"real":60,"workHour":8},{"real":52,"workHour":9},{"real":114,"workHour":10},{"real":115,"workHour":11},{"real":92,"workHour":12},{"real":102,"workHour":13},{"real":54,"workHour":14},{"real":62,"workHour":15},{"real":133,"workHour":16},{"real":116,"workHour":17},{"real":106,"workHour":18},{"real":115,"workHour":19},{"real":115,"workHour":20},{"real":125,"workHour":21}];
var map = {};
for (var i=0; i<array.length; i++) {
var obj = array[i],
id = obj.workHour;
if (id in map) { // we know this id already
// get the object and sum properties
map[id].real += obj.real;
} else // create a new one
map[id] = {
workHour: id,
real: obj.real,
};
}
console.log(map)
How can I do it with ES6? or Underscore?
Loop the array with Array#reduce to create a new object with combined values:
var data = [{"real":104.1530776708426,"workHour":8,"value":null},{"real":71.53948769310401,"workHour":9},{"real":97.84076993321577,"workHour":10},{"real":115.72564185649178,"workHour":11},{"real":79.95589800993977,"workHour":12},{"real":91.52846219558896,"workHour":13},{"real":57.86282092824589,"workHour":14},{"real":148.33923183423036,"workHour":15},{"real":125.19410346293202,"workHour":16},{"real":67.33128253468612,"workHour":17},{"real":55.75871834903695,"workHour":18},{"real":102.04897509163365,"workHour":19},{"real":132.55846249016332,"workHour":20},{"real":138.87077022779013,"workHour":21},{"real":60,"workHour":8},{"real":52,"workHour":9},{"real":114,"workHour":10},{"real":115,"workHour":11},{"real":92,"workHour":12},{"real":102,"workHour":13},{"real":54,"workHour":14},{"real":62,"workHour":15},{"real":133,"workHour":16},{"real":116,"workHour":17},{"real":106,"workHour":18},{"real":115,"workHour":19},{"real":115,"workHour":20},{"real":125,"workHour":21}];
var result = data.reduce(function(r, o) {
if (r[o.workHour]) {
r[o.workHour].real += o.real
} else {
r[o.workHour] = {
workHour: o.workHour,
real: o.real
}
}
return r;
}, {});
console.log(result);
Using lodash(/underscore), this uses _.reduce() with _.clone() to build an aggregate object. The values of this object are then output as an array of objects similar to your input array using _.values().
var data = [{"real":104.1530776708426,"workHour":8,"value":null},{"real":71.53948769310401,"workHour":9},{"real":97.84076993321577,"workHour":10},{"real":115.72564185649178,"workHour":11},{"real":79.95589800993977,"workHour":12},{"real":91.52846219558896,"workHour":13},{"real":57.86282092824589,"workHour":14},{"real":148.33923183423036,"workHour":15},{"real":125.19410346293202,"workHour":16},{"real":67.33128253468612,"workHour":17},{"real":55.75871834903695,"workHour":18},{"real":102.04897509163365,"workHour":19},{"real":132.55846249016332,"workHour":20},{"real":138.87077022779013,"workHour":21},{"real":60,"workHour":8},{"real":52,"workHour":9},{"real":114,"workHour":10},{"real":115,"workHour":11},{"real":92,"workHour":12},{"real":102,"workHour":13},{"real":54,"workHour":14},{"real":62,"workHour":15},{"real":133,"workHour":16},{"real":116,"workHour":17},{"real":106,"workHour":18},{"real":115,"workHour":19},{"real":115,"workHour":20},{"real":125,"workHour":21}];
var result = _.values(_.reduce(data, (sumObj, curr) => {
if (sumObj[curr.workHour])
sumObj[curr.workHour].real += curr.real;
else
sumObj[curr.workHour] = _.clone(curr);
return sumObj;
}, {}));
console.log(result);
.as-console-wrapper{top:0;max-height:100%!important;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
I have stored group of objects into one array called 'resData' and i'm having one more array of data called 'approvedIds', there have included all approved id's. Here i want to match these two arrays and add one new key into 'resData' array like 'approveStatus:"approve"'. How to do this one in javascript?
All data's,
var resData = [
{
firstName:"Jhon",
lastName:"adam",
emailId:"jhn12#gmail.com",
id:"01"
},
{
firstName:"Kyle",
lastName:"Miller",
emailId:"kl12#gmail.com",
id:"02"
},
{
firstName:"Jhonathan",
lastName:"adam",
emailId:"jadm12#gmail.com",
id:"03"
},
{
firstName:"Lewis",
lastName:"harber",
emailId:"lewh12#gmail.com",
id:"04"
}
];
Approved id's array,
var approvedIds = ['01', '03'];
My output will be like this,
var resData = [
{
firstName:"Jhon",
lastName:"adam",
emailId:"jhn12#gmail.com",
id:"01",
approveStatus:'approved'
},
{
firstName:"Kyle",
lastName:"Miller",
emailId:"kl12#gmail.com",
id:"02"
},
{
firstName:"Jhonathan",
lastName:"adam",
emailId:"jadm12#gmail.com",
id:"03",
approveStatus:'approved'
},
{
firstName:"Lewis",
lastName:"harber",
emailId:"lewh12#gmail.com",
id:"04"
}
];
You can try this. Use forEach and indexOf functions
var resData = [
{
firstName:"Jhon",
lastName:"adam",
emailId:"jhn12#gmail.com",
id:"01"
},
{
firstName:"Kyle",
lastName:"Miller",
emailId:"kl12#gmail.com",
id:"02"
},
{
firstName:"Jhonathan",
lastName:"adam",
emailId:"jadm12#gmail.com",
id:"03"
},
{
firstName:"Lewis",
lastName:"harber",
emailId:"lewh12#gmail.com",
id:"04"
}
];
var approvedIds = ['01', '03'];
resData.forEach(item => {
if(approvedIds.indexOf(item.id) !== -1){
item.approvedStatus = 'approved';
}
} );
console.log(resData);
Using ES6 array functions, which is more functional and doesn't alter the original objects:
var resData = [
{
firstName:"Jhon",
lastName:"adam",
emailId:"jhn12#gmail.com",
id:"01"
},
{
firstName:"Kyle",
lastName:"Miller",
emailId:"kl12#gmail.com",
id:"02"
},
{
firstName:"Jhonathan",
lastName:"adam",
emailId:"jadm12#gmail.com",
id:"03"
},
{
firstName:"Lewis",
lastName:"harber",
emailId:"lewh12#gmail.com",
id:"04"
}
];
var approvedIds = ['01', '03'];
//Solution:
var newData = resData
.filter(rd => approvedIds.indexOf(rd.id) >= 0)
.map(rd => Object.assign({}, rd, {approvedStatus: "approved"}));
console.log(newData, resData);
I have a object like so:
$scope.query = {
filter: {
column: {
productName: 'Some Product',
price: 29.95,
...
},
table: {
productType: 'GM',
categoryId: 1,
...
}
}
};
How do I get a string that represents the whole object in dot notation? e.g.
query.filter.table.productType
To clarify, I am using this string value as a key to store a key/value pair in localStorage.
I am using angular to $wacth each property on the object for a change. Since you can't watch an object and know which property changed with watching all, I need to get creative and store each property in a key/value pair.
You can do it recursively, and produces "key" in an array.
var obj = {
query: {
filter: {
table: {
productType: 'GM'
}
}
}
};
var stringify = function (e) {
var rs = [];
for (var k in e) {
if (e.hasOwnProperty(k)) {
if (typeof e[k] == 'object') {
var l = stringify(e[k]);
for (var i = 0; i < l.length; i++) {
rs.push(k + '.' + l[i]);
}
} else {
rs.push(k);
}
}
}
return rs;
}
console.log(stringify(obj));
outputs:
["query.filter.table.productType"]
fiddle
Demo
Before Ques Edit
var $scope = {
query: {
filter: {
table: {
productType: 'GM'
}
}
}
};
var k = JSON.stringify($scope)
//output "{"query":{"filter":{"table":{"productType":"GM"}}}}"
k.match(/\w+(?=\"\:)/g).join('.')
//output"query.filter.table.productType"
Edit
Updated Demo
If OP has no issue with the position of child elements
var $scope = {}
$scope.query = {
filter: {
column: {
productName: 'Some Product',
price: 29.95
},
table: {
productType: 'GM',
categoryId: 1,
}
}
};
k=JSON.stringify($scope)
{"query":{"filter":{"column":{"productName":"Some Product","price":29.95},"table":{"productType":"GM","categoryId":1}}}}
k.match(/\w+(?=\"\:)/g).join('.')
"query.filter.column.productName.price.table.productType.categoryId"
By iterating the properties into an array recursively you could create a hierarchical structure that represents the data in the object. From here you could parse the results out as you wish.
var scope = {
query: {
filter: {
column: {
productName: 'Some Product',
price: 29.95
},
table: {
productType: 'GM',
categoryId: 1
}
}
}
};
function buildProps(subject) {
var result = [];
for (var key in subject) {
if (subject.hasOwnProperty(key)) {
if (typeof subject[key] == "object") {
result.push(key, buildProps(subject[key]));
} else {
result.push(key);
}
}
}
return result;
}
function stringify(input) {
var result = [];
for (var i = 0; i < input.length; i++) {
if (typeof input[i] == "string") {
result.push(input[i]);
} else {
result = result.concat(stringify(input[i]));
}
}
return result.join('.');
}
console.log(buildProps(scope));
console.log(stringify(buildProps(scope)));
Parse out the strings in the resulting array/sub-arrays, format it any way you like.
In my simple example I just list them in order:
query.filter.column.productName.price.table.productType.categoryId