Ordering recursive function results in arrays of arrays - javascript

I am currently dealing with in issue in writing a recrusive function to order some json data. I have several nested arrays of objects which i need to order into single slides. The structure is similar to the following :
[
{
"title": "a",
"children": [
{
"title": "a-a",
"children": [
{
"title": "a-a-a"
},
{
"title": "a-a-b"
}
]
},
{
"title": "a-b",
"children": [
{
"title": "a-b-a"
},
{
"title": "a-b-b"
}
]
}
]
},
{
"title": "b",
"children": [
{
"title": "b-a",
"children": [
{
"title": "b-a-a"
},
{
"title": "b-a-b"
}
]
},
{
"title": "b-b",
"children": [
{
"title": "b-b-a"
},
{
"title": "b-b-b"
}
]
}
]
}
]
I have written a recursive function :
var catalog = {
init: function() {
var _this = this;
$.getJSON("catalog.json", function(data) {
_this.slides = [];
_this.parseCategories(data.catalog.category,-1,0);
});
},
parseCategories: function(array, depth, prevParent) {
++depth;
if (!this.slides[depth]) this.slides[depth] = [];
if (!this.slides[depth][prevParent]) this.slides[depth][prevParent] = [];
this.slides[depth][prevParent].push(array);
for (var i = 0; i < array.length; i++) {
if (array[i].category) {
this.parseCategories(array[i].category, depth, i);
}
}
}
}
catalog.init();
This outputs :
However instead of retrieving the data for my third slide under format :
a-a-a
a-b-a
a-c-a
I would like to get
a-a-[a,b,c]
I was wondering if that was possible since I'm not very good at handling recursive processes. I hope I was clear and thank you for reading this.
I basically need to keep my original data structure but remove the first depth level for each iteration (slide in a slider that represent increasing depths in my data structure).

I recently wrote a algorithm to recursively handle data like this. Here is a jsfiddle and the main function
console.log('starting');
// data in tree format.
var output = {};
// data in slide format ["a-a-a", "a-a-b", "b-b-a", "b-b-b"]
var outputStrs = [];
parseData(data, output);
console.log(output);
console.log(outputStrs);
function parseData(data, store) {
// go through each element
for (var i = 0; i < data.length; i++) {
var element = data[i];
// used to keep track of where we are in the tree.
var splitElement = element.title.split('-');
var titleStart = splitElement[0];
// console.log(element);
if (_.has(element, 'children') && _.isArray(element.children)) {
// if there is a children, then recursively handle it.
store[titleStart] = {};
parseData(element.children, store[titleStart]);
} else {
// if we are at the end, then add in the data differently.
var titleEnd = splitElement[splitElement.length-1];
store[titleEnd] = titleEnd;
// create the slides
var slide = [];
for (var j = 0; j < splitElement.length; j++) {
if (j !== splitElement.length - 1) {
slide.push(titleStart);
} else {
slide.push(titleEnd);
}
}
slide = slide.join('-');
if (!_.contains(outputStrs, slide)) outputStrs.push(slide);
}
}
}
With this data the output should resemble
a
a
a
b
b
b
a
b
And outputStrs will resemble a-a-[a,b,c]
Hope this helps!!!

Related

javascript grouping linked nodes

I have 2 lists - nodes and links... Now what I would want is the most efficient way to add all the directly/indirectly linked elements into different groups.... For eg, 0 is connected to 1 which is connected to 2 so nodes 0,1,2 become group 1.... node 3 is connected to 4 so it becomes group 2 and so on.... Thanks in advance for your help :) This is part of a d3 implementation am doing..
PS: These lists will easily be in tens of thousands of nodes and links.
"nodes":[
{
"id":0,
"x":1509.9862,
"y":-609.1013
},
{
"id":1,
"x":1645.9578,
"y":-85.06705
},
{
"id":2,
"x":1948.1533,
"y":-469.3646
},
{
"id":3,
"x":348.1533,
"y":-669.3646
},
{
"id":4,
"x":1448.1533,
"y":-1469.3646
}
...
]
"links":[
{
"from":0,
"to":1
},
{
"from":1,
"to":2
},
{
"from":3,
"to":4
}
...
]
This is a classic UnionFind problem. The idea is to see each node as a set that has a pointer point to its father. Nodes with the same father are in the same group. So for your problem, we can create n sets at the beginning. And then iterate through the link to group everyone connected by the same link. The complexity is O(n), where n is the number of nodes.
nodes = [{
"id": 0,
"x": 1509.9862,
"y": -609.1013
},
{
"id": 1,
"x": 1645.9578,
"y": -85.06705
},
{
"id": 2,
"x": 1948.1533,
"y": -469.3646
},
{
"id": 3,
"x": 348.1533,
"y": -669.3646
},
{
"id": 4,
"x": 1448.1533,
"y": -1469.3646
}
];
links = [{
"from": 0,
"to": 1
},
{
"from": 1,
"to": 2
},
{
"from": 3,
"to": 4
}
];
// union-find is a data structure that can union two sets and check
// whether two element in the same set.
var father = {};
function group(nodes, links) {
// create n set with each set has the node as its only element
nodes.forEach(function(node, i) {
father[node.id] = node.id;
});
// union each set that has a link between them
links.forEach(function(link, i) {
union(link.from, link.to);
});
// for each unioned set, group nodes together
var id = 1;
var groupIdCnt = {};
var groupIds = {};
nodes.forEach(function(node, i) {
var f = find(node.id);
if (typeof groupIds[f] === 'undefined') {
groupIds[f] = id;
groupIdCnt[id] = 1;
id++;
} else {
groupIdCnt[groupIds[f]]++;
}
});
var groups = {};
nodes.forEach(function(node, i) {
var f = find(node.id);
if (groupIdCnt[groupIds[f]] === 1) {
node['group'] = 0;
} else {
node['group'] = groupIds[f];
}
if (typeof groups[node['group']] === 'undefined') {
groups[node['group']] = [];
}
groups[node['group']].push(node);
});
return Object.values(groups);
}
// find father of each set
function find(node) {
// if it is the root, return
if (father[node] === node) {
return node;
}
// if not, find the father and point to it
father[node] = find(father[node]);
return father[node];
}
// update the father of set which includes node1 to the father of set which includes node 2
function union(node1, node2) {
father[find(node1)] = find(node2);
}
// O(n), since we visit each node once
var groups = group(nodes, links);
console.log(nodes);
console.log(groups);
This code spits out an object whose keys are the node id and whose values are a group id, not necessarily sequential.
var obj = {
"links":[
{
"from":0,
"to":1
},
{
"from":1,
"to":2
},
{
"from":5,
"to":4
},
{
"from":3,
"to":4
}
]
};
var groups = {};
var nextGrp = 1;
for (var i=0, l; l = obj.links[i]; i++) {
if (groups[l.from]) {
if (groups[l.to]) {
if (groups[l.to] != groups[l.from]) {
// the two items span two different groups which must now be joined into 1
for (var j in groups) {
if (groups[j] == groups[l.to]) {
groups[j] = groups[l.from];
}
}
}
} else {
groups[l.to] = groups[l.from];
}
} else if (groups[l.to]) {
groups[l.from] = groups[l.to];
} else {
groups[l.from] = nextGrp;
groups[l.to] = nextGrp;
nextGrp++;
}
}
console.log(groups);
In the solution below I am creating groups of links that are, well, linked to each other. I do so by looping through all of the from/to combinations, and finding out if either has already been added to any of the accumulating groups of links. If they have, then I just add (or re-add) both the from and to value to that group. If neither the from nor to value has yet been grouped, then I make a new group and add both the from and to values to it. Note that these "groups" are actually Javascript sets, a new ES6/ES2015 data type that makes it much easier to deal with "groups" of elements for which no duplicates are needed and/or allowed.
Once the groups/sets of links are established, I then simply add an attribute to each node that indicates which group of links it is a part of.
Note that, for the sake of this demo code, I've simplified/de-cluttered some node values. I've also added some extra links, just to demonstrate some further cases that need handling.
const groupNodes = (nodes, links) => {
const groups = links.reduce((grps, {from, to}) => {
if (!grps.some(grp => {
if (grp.has(from) || grp.has(to)) return grp.add(from).add(to);
})) grps.push(new Set([from, to]));
return grps;
}, []);
nodes.forEach(node => {
groups.forEach((grp, i) => { if (grp.has(node.id)) node.group = i; });
});
return nodes;
};
const nodes = [
{
"id":0,
"x":0,
"y":0
},
{
"id":1,
"x":11,
"y":-11
},
{
"id":2,
"x":22,
"y":-22
},
{
"id":3,
"x":33,
"y":-33
},
{
"id":4,
"x":44,
"y":-44
},
{
"id":5,
"x":55,
"y":-55
},
{
"id":6,
"x":66,
"y":-66
}
];
const links = [
{
"from": 0,
"to" : 1
},
{
"from": 1,
"to" : 2
},
{
"from": 2,
"to" : 0
},
{
"from": 3,
"to" : 4
},
{
"from": 4,
"to" : 5
},
{
"from": 6,
"to" : 0
}
];
console.log(JSON.stringify(groupNodes(nodes, links)));

Data manipulation in DataTables

I have a quite complex data manipulation to perform.
My datasource gives me a list of cashflows, grouped by person like that:
{
"months": [
"2016-10-01",
"2016-11-01",
"2016-12-01",
"2017-01-01"
],
"persons": [
{
"label": "John",
"cashflows": [
{
"date": "2016-10-01",
"amount": "1000.00"
},
{
"date": "2016-11-01",
"amount": "1000.00"
}
]
},
{
"label": "Brad",
"cashflows": [
{
"date": "2017-01-01",
"amount": "5540.00"
}
]
}
]
}
I want to put those data in a DataTable, but I don't know how to "JOIN" the months and the cashflows.
My best guest is a sql-like query, but in javascript, in order to perform this pseudo-code:
select each person
for each person
good_row = person.cashflows LEFT JOIN months ON cashflows.date (iiish..)
I have set up a jsfiddle here.
Here is the plain javascript way to do it (the hard way).
Fiddle link: https://jsfiddle.net/ngwqfjo0/
function getDesiredData() {
var persons = real_data["persons"];
var months = real_data["months"];
persons.forEach(function(person) {
var row = [];
var amounts = [];
row.push(person["label"]);
months.forEach(function(month) {
var amount = '';
for(x = 0; x < person["cashflows"].length; x++) {
if(month == person["cashflows"][x]["date"]) {
amount = person["cashflows"][x]["amount"];
break;
}
}
amounts.push(amount);
});
desiredData.push(row.concat(amounts));
});
return desiredData;
}
To make life easier, consider using a functional utility like lodash or underscore
function getDesiredDataEasy() {
var persons = real_data["persons"];
var months = real_data["months"];
var desiredData = [];
return _.map(persons, function(person) {
return _.concat([person["label"]], _.map(months, function(month) {
var cashFlowDate = _.find(person["cashflows"], function(cf) {
return cf.date == month;
});
return cashFlowDate ? cashFlowDate.amount : "";
}));
});
}

JSON data transformation from table-like format to JSON?

I have data that's in this format:
{
"columns": [
{
"values": [
{
"data": [
"Project Name",
"Owner",
"Creation Date",
"Completed Tasks"
]
}
]
}
],
"rows": [
{
"values": [
{
"data": [
"My Project 1",
"Franklin",
"7/1/2015",
"387"
]
}
]
},
{
"values": [
{
"data": [
"My Project 2",
"Beth",
"7/12/2015",
"402"
]
}
]
}
]
}
Is there some super short/easy way I can format it like so:
{
"projects": [
{
"projectName": "My Project 1",
"owner": "Franklin",
"creationDate": "7/1/2015",
"completedTasks": "387"
},
{
"projectName": "My Project 2",
"owner": "Beth",
"creationDate": "7/12/2015",
"completedTasks": "402"
}
]
}
I've already got the column name translation code:
r = s.replace(/\%/g, 'Perc')
.replace(/^[0-9A-Z]/g, function (x) {
return x.toLowerCase();
}).replace(/[\(\)\s]/g, '');
Before I dive into this with a bunch of forEach loops, I was wondering if there was a super quick way to transform this. I'm open to using libraries such as Underscore.
function translate(str) {
return str.replace(/\%/g, 'Perc')
.replace(/^[0-9A-Z]/g, function (x) {
return x.toLowerCase();
})
.replace(/[\(\)\s]/g, '');
}
function newFormat(obj) {
// grab the column names
var colNames = obj.columns[0].values[0].data;
// create a new temporary array
var out = [];
var rows = obj.rows;
// loop over the rows
rows.forEach(function (row) {
var record = row.values[0].data;
// create a new object, loop over the existing array elements
// and add them to the object using the column names as keys
var newRec = {};
for (var i = 0, l = record.length; i < l; i++) {
newRec[translate(colNames[i])] = record[i];
}
// push the new object to the array
out.push(newRec);
});
// return the final object
return { projects: out };
}
DEMO
There is no easy way, and this is really not that complex of an operation, even using for loops. I don't know why you would want to use regex to do this.
I would start with reading out the column values into a numerically indexed array.
So something like:
var sourceData = JSON.parse(yourJSONstring);
var columns = sourceData.columns[0].values[0].data;
Now you have a convenient way to start building your desired object. You can use the columns array created above to provide property key labels in your final object.
var sourceRows = sourceData.rows;
var finalData = {
"projects": []
};
// iterate through rows and write to object
for (i = 0; i < sourceRows.length; i++) {
var sourceRow = sourceRows[i].values.data;
// load data from row in finalData object
for (j = 0; j < sourceRow.length; j++) {
finalData.projects[i][columns[j]] = sourceRow[j];
}
}
That should do the trick for you.

JavaScript compare two arrays(key/value pairs) and copy value from one to the other if key matches

I have two arrays containing key/value pairs.
{
"containerOne": [{
"Id": 1,
"Title": "TitleOne",
"Responsibility": "ValueOne"
}, {
"Id": 2,
"Title": "TitleTwo",
"Responsibility": "ValueTwo"
}]
}
{
"containerTwo": [{
"Id": 1,
"Title": "TitleOne",
"Responsibility": null
}, {
"Id": 2,
"Title": "TitleTwo",
"Responsibility": "null
}
]
}
I'd like to compare both arrays and compare the title of each container. If the titles match, then I'd like to copy the Responsibility value from containerOne to containerTwo. The ID's will not match, so that can be ruled out. Only the titles will be consistent.
What is the most efficient way to do this please?]
Thanks
=====================================================================
EDIT
=====================================================================
Looking at the arrays a little closer, there is a subtle difference in the data being returned:
{
"AMLookupTasksList":
[
{
"Id":1,
"Title":"Create and Maintain an Onboarding Document",
"Responsibility":"1. Onboarding|f101ccf1-c7d5-42e7-ba8f-48e88ac90a3d"
},
{
"Id":2,
"Title":"Execute Onboarding for New Consultants",
"Responsibility":"1. Onboarding|f101ccf1-c7d5-42e7-ba8f-48e88ac90a3d"
}
]
}
{
"AMTasksList":
[
{
"Id":4,
"Title":
{
"$M_1":13,"$c_1":"Create and Maintain an Onboarding Document"
},
"Responsibility":null
},
{
"Id":17,
"Title":
{
"$M_1":12,"$c_1":"Execute Onboarding for New Consultants"
},
"Responsibility":null
}
]
}
Do I have additional looping to get to the Title value in the second array?
This might be a bit of overkill but it ignores the sequence and does a look up in each object.
I had to fix some syntax in your objects but I include that: named the objects and took a quote off one of the null values.
var obj1 = {
"containerOne": [{
"Id": 1,
"Title": "TitleOne",
"Responsibility": "ValueOne"
}, {
"Id": 2,
"Title": "TitleTwo",
"Responsibility": "ValueTwo"
}]
};
var obj2 = {
"containerTwo": [{
"Id": 1,
"Title": "TitleOne",
"Responsibility": null
}, {
"Id": 2,
"Title": "TitleTwo",
"Responsibility": null
}]
};
Now the code:
// lookup for first object:
var lookup = {};
// create referece to list above and use it everywhere
lookup.list = obj1;
for (var i = 0, len = lookup.list.containerOne.length; i < len; i++) {
lookup[lookup.list.containerOne[i].Title] = lookup.list.containerOne[i];
}
// lookup for second object
var otherLookup = {};
otherLookup.list = obj2;
for (var i = 0, len = otherLookup.list.containerTwo.length; i < len; i++) {
otherLookup[otherLookup.list.containerTwo[i].Title] = otherLookup.list.containerTwo[i];
}
// copy value for Responsibility from first to second on each matched in second.
var i = 0;
var len = lookup.list.containerOne.length;
for (i; i < len; i++) {
// looks up value from second list in the first one and if found, copies
if (lookup[otherLookup.list.containerTwo[i].Title]) {
otherLookup.list.containerTwo[i].Responsibility = lookup[otherLookup.list.containerTwo[i].Title].Responsibility;
}
}
// alerts new value using lookup
alert(otherLookup["TitleOne"].Responsibility);
EDIT for new structure, but same answer really:
var obj1 = {
"AMLookupTasksList": [{
"Id": 1,
"Title": "Create and Maintain an Onboarding Document",
"Responsibility": "1. Onboarding|f101ccf1-c7d5-42e7-ba8f-48e88ac90a3d"
}, {
"Id": 2,
"Title": "Execute Onboarding for New Consultants",
"Responsibility": "1. Onboarding|f101ccf1-c7d5-42e7-ba8f-48e88ac90a3d"
}]
};
var obj2 = {
"AMTasksList": [{
"Id": 4,
"Title": {
"$M_1": 13,
"$c_1": "Create and Maintain an Onboarding Document"
},
"Responsibility": null
}, {
"Id": 17,
"Title": {
"$M_1": 12,
"$c_1": "Execute Onboarding for New Consultants"
},
"Responsibility": null
}]
};
var lookup = {};
// create refernece to list above and use it everywhere
lookup.list = obj1;
for (var i = 0, len = lookup.list.AMLookupTasksList.length; i < len; i++) {
lookup[lookup.list.AMLookupTasksList[i].Title] = lookup.list.AMLookupTasksList[i];
}
var otherLookup = {};
otherLookup.list = obj2;
for (var i = 0, len = otherLookup.list.AMTasksList.length; i < len; i++) {
otherLookup[otherLookup.list.AMTasksList[i].Title.$c_1] = otherLookup.list.AMTasksList[i];
}
// copy value for Responsibility from first to second
var i = 0;
var len = otherLookup.list.AMTasksList.length;
for (i; i < len; i++) {
if (lookup[otherLookup.list.AMTasksList[i].Title.$c_1]) {
otherLookup.list.AMTasksList[i].Responsibility = lookup[otherLookup.list.AMTasksList[i].Title.$c_1].Responsibility;
}
}
alert(otherLookup["Create and Maintain an Onboarding Document"].Responsibility);
Fiddle for second answer: http://jsfiddle.net/n22V8/
First, create a dictionary from containerTwo:
var c2dict = {};
var c2i = containerTwo.innerContainer;
for (var i = 0; i < c2i.length; i++) {
c2dict[c2i[i].Title] = c2i[i];
}
Now use this to do the copying of propertyies when titles match:
var c1i = containerOne.innerContainer;
for (var i = 0; i < c1i.length; i++) {
if (c2dict[c1i[i].Title]) {
c2dict[c1i[i].Title].Property = c1i[i].Property;
}
}
You should compare properties and set them as the following:
containerOne.innerContainer.forEach(function (element,index) {
containerTwo.innerContainer.forEach(function (element2,index2) {
if (element.Title === element2.Title && element.Property != element2.Property) {
element2.Property = element.Property;
}
});
});

JSON tree to parent-link structure

I have a JSON tree structure:
nodes =
[
{
"name": "user1",
"children": [
{
"name": "user2"
},
{
"name": "user3",
"children": [
{
"name": "user4"
}
]
},
{
"name": "user5"
}
]
}
]
that I would like to convert to a parent-link structure:
[{"name": "user1","parent": "null"},
{"name": "user2","parent": "user1"},
{"name": "user3","parent": "user1"},
{"name": "user4","parent": "user3"},
{"name": "user5","parent": "user1"}]
I have tried to traverse the tree recursively but without success accessing the parent object:
rebuild(nodes,parentLink);
function parentlink(key,value) {
var obj = { name: value , parent: ??? };
if (key == "name"){
nodes.push(obj);
}
}
function rebuild(o,func) {
for (i in o) {
func.apply(this,[i,o[i]])
if (typeof(o[i])=="object") {
traverse(o[i],func,nodes);
}
}
}
In developer tools I can see the parent objects for each child, but I don't know how to access them. What should I do to add the parent to each user?
I'm not gonna lie, I didn't bother looking at your code - this is how I would do it:
http://jsfiddle.net/J6G2W/1/
function processChildren(item, ret, parent) {
for (var i = 0; i < item.length; i++) {
var cur = item[i];
var cur_name = cur.name;
ret.push({"user": cur_name, "parent": parent});
if ("children" in cur && cur.children.length > 0) {
processChildren(cur.children, ret, cur_name);
}
}
}
var all = [];
processChildren(nodes, all, null);
console.log(JSON.stringify(all));
Output is:
[{"user":"user1","parent":null},{"user":"user2","parent":"user1"},{"user":"user3","parent":"user1"},{"user":"user4","parent":"user3"},{"user":"user5","parent":"user1"}]
Which seems to be what you're looking for. You're welcome to modify what of my code to work more like yours, I just thought I'd share what I'd do :)
UPDATE:
If, for some reason, you want to make it more extendable, you can customize which keys are the "name" and which are the "children"...for example:
http://jsfiddle.net/J6G2W/2/
function startProcess(item, ret, key_look, children_look, parent) {
function processChildren(item2, ret2, parent2) {
for (var i = 0; i < item2.length; i++) {
var cur = item2[i];
var cur_name = key_look in cur ? cur[key_look] : null;
ret.push({"user": cur_name, "parent": parent2});
if (children_look in cur && cur[children_look].length > 0) {
processChildren(cur[children_look], ret, cur_name);
}
}
}
processChildren(item, ret, parent);
}
var all = [];
startProcess(nodes, all, "name", "children", null);
console.log(JSON.stringify(all));
Notice how you only have to specify the key_look, children_look arguments once. The inner function can access those parameters while only passing the important things each recursion. This probably isn't important, I just wanted to figure it out :)

Categories