I am having a problem with my algorithm in javascript. Basically, I am building a graph and I need to keep reference on subgraph. The problem is that from one iteration to other, I loose this reference and I still unable to understand while this behavior. This is my code.
<!DOCTYPE html>
<html>
<body>
<script>
const SIZE = 1000;
var graph = {
"name": "flare",
"children": [ ]
}
var testjson = {
"angular-chart.js#1.0.3": {
"chart.js#2.3.0": {
"chartjs-color#2.0.0": {
"chartjs-color-string#0.4.0": {
"color-name#1.1.1": {}
},
"color-convert#0.5.3": {}
},
"moment#2.15.2": {}
},
"angular#1.5.8": {}
}
};
function traverse_it(obj, myFunc){
var ref = graph;// .children;
for(var prop in obj){
if( !Object.keys(obj[prop]).length){
console.log("{}");
ref.push({'name': prop, 'size': SIZE });
return;
}
else if(typeof obj[prop]==='object'){
console.log("------------------------------");
console.log(typeof ref);
console.log('ref in the ginning of iteration');
console.log(ref);
ref = ref.children;
console.log(ref);
ref.push({'name': prop, 'children': [] });
console.log(ref);
ref = ref[ref.length -1];
console.log("--------graph----------------");
console.log(graph);
console.log("---------graph----------------");
console.log('ref at the end of iteration');
console.log(ref);
console.log("------------------------------");
if (graph.children.length > 1)
return;
traverse_it(obj[prop]);
}else{
console.log("error ");
throw err;
}
}
return;
}
traverse_it(testjson);
</script>
</body>
</html>
The output is something like this:
------------------------------
object
ref in the ginning of iteration
{
"name": "flare",
"children": []
}
[]
[
{
"name": "angular-chart.js#1.0.3",
"children": []
}
]
--------graph----------------
{
"name": "flare",
"children": [
{
"name": "angular-chart.js#1.0.3",
"children": []
}
]
}
---------graph----------------
ref at the end of iteration
{
"name": "angular-chart.js#1.0.3",
"children": []
}
------------------------------
------------------------------
object
ref in the ginning of iteration
{
"name": "flare",
"children": [
{
"name": "angular-chart.js#1.0.3",
"children": []
}
]
}
[
{
"name": "angular-chart.js#1.0.3",
"children": []
}
]
[
{
"name": "angular-chart.js#1.0.3",
"children": []
},
{
"name": "chart.js#2.3.0",
"children": []
}
]
--------graph----------------
{
"name": "flare",
"children": [
{
"name": "angular-chart.js#1.0.3",
"children": []
},
{
"name": "chart.js#2.3.0",
"children": []
}
]
}
---------graph----------------
ref at the end of iteration
{
"name": "chart.js#2.3.0",
"children": []
}
------------------------------
As you can see in this output, ref is changing from iteration 1 to 2. How can I keep the same value of ref (subgraph) at the end of iteration and begin of new iteration.
Related
I have a JSON array of the following format (this data is pulled from mongodb to be displayed as a tree graph on a react-based front-end):
[
{
"name": "7654321",
"children": [
{
"_id": "LjYgocn9PsHhEFbM7",
"accountId": "4343213"
},
{
"_id": "sB2ipCstYnLnHrAuu",
"accountId": "4343271"
},
{
"_id": "JhugmhxS7A57Y34wM",
"accountId": "4343276"
}
]
},
{
"name": "4343213",
"children": [
]
},
{
"name": "4343271",
"children": [
{
"_id": "sie9mtttgdRw7Ktma",
"accountId": "4343279"
}
]
},
{
"name": "4343279",
"children": [
{
"_id": "sie23mtttgdRw7Ktma",
"accountId": "8765345"
}
]
},
{
"name": "4343276",
"children": [
]
}
]
The goal is to re-format (rename and delete some keys) this data to be used in react-tree-graph. From the sample above, output should look like:
[
{
"name": "7654321",
"children": [
{
"name": "4343213"
},
{
"name": "4343271",
"children": [
{
"name": "4343279",
"children": [
{
"name": "8765345"
}
]
}
]
},
{
"name": "4343276"
}
]
}
]
Any help is appreciated!
You could first create a Map that has as keys the name property values, and as corresponding values the (unfinished) result objects. They start off with just the name property.
Then you can iterate the children information in the input, to wire the children into the above mentioned result objects, which can be done efficiently using the name as key in the Map.
Whenever you wire a child object into a parent object, you know that child is not a top-level object in the final result. So starting with all nodes, you would trim that list (a Set) all those nodes that occur in a children array. This will leave you with only the top level nodes, which in its array form represents the desired output.
Implementation:
let data = [{"name": "7654321","children": [{"_id": "LjYgocn9PsHhEFbM7","accountId": "4343213"},{"_id": "sB2ipCstYnLnHrAuu","accountId": "4343271"},{"_id": "JhugmhxS7A57Y34wM","accountId": "4343276"}]},{"name": "4343213","children": []},{"name": "4343271","children": [{"_id": "sie9mtttgdRw7Ktma","accountId": "4343279"}]},{"name": "4343279","children": [{"_id": "sie23mtttgdRw7Ktma","accountId": "8765345"}]},{"name": "4343276","children": []}];
let map = new Map(data.map(({name, children}) => [name, { name }]));
let roots = new Set(map.values());
for (let {name, children} of data) {
if (!children?.length) continue;
map.get(name).children = children.map(({accountId}) => {
let child = map.get(accountId) || { name: accountId };
roots.delete(child);
return child;
});
}
let result = Array.from(roots);
console.log(result);
var obj = [
{
"name": "A1",
"children": [
{
"name": "A1-level1-child1",
"children": [
{
"name": "A1-level2-child1",
"children": [
{
"name": "A1-level3-child1",
"children": []
},
{
"name": "A1-level3-child2",
"children": []
}
]
}
]
},
{
"name": "A2-level1-child1",
"children": []
}
]
},
{
"name": "B1",
"children": [
]
}
];
From the above JSON object, if i check the value "A1-level3-child1", the function should give me its parent name as "A1-level2-child1". Same way, if i check for "A2-level1-child1",then it should be give me the parent value as "A1".
You could iterate the array or children and use a short circuit if the node is found.
function getParentName(array, name, parent = 'root') {
var result;
array.some(o => result = o.name === name && parent
|| o.children && getParentName(o.children, name, o.name));
return result;
}
var array = [{ name: "A1", children: [{ name: "A1-level1-child1", children: [{ name: "A1-level2-child1", children: [{ name: "A1-level3-child1", children: [] }, { name: "A1-level3-child2", children: [] }] }] }, { name: "A2-level1-child1", children: [] }] }, { name: "B1", children: [] }];
console.log(getParentName(array, "A1-level3-child1")); // A1-level2-child1
console.log(getParentName(array, "A2-level1-child1")); // A1
GoGo this code.
var parentMap = {}
function getParentMap(arr, parent) {
if (!(arr instanceof Array)) {
return;
}
for (o of arr) {
parentMap[o.name] = parent;
if (o.children && o.children.length) {
getParentMap(o.children, o);
//getParentMap(o.children, o.name);
}
}
}
var arr = [{
"name": "A1",
"children": [{
"name": "A1-level1-child1",
"children": [{
"name": "A1-level2-child1",
"children": [{
"name": "A1-level3-child1",
"children": []
},
{
"name": "A1-level3-child2",
"children": []
}
]
}]
},
{
"name": "A2-level1-child1",
"children": []
}
]
},
{
"name": "B1",
"children": []
}];
getParentMap(obj, null);
parentMap["A1-level3-child1"].name
If you can redefine this structure, you can add 'parent' on every node, so more easy to operate it.
I wrote this script to convert a nested array with the structure below to a nested object with parent child relationships.
list = [
['lvl-1 item-1', 'lvl-2 item-1'],
['lvl-1 item-1', 'lvl-2 item-1', 'lvl-3 item-1'],
['lvl-1 item-1', 'lvl-2 item-1', 'lvl-3 item-2'],
['lvl-1 item-2', 'lvl-2 item-1', 'lvl-3 item-1'],
['lvl-1 item-2', 'lvl-2 item-2', 'lvl-3 item-2', 'lvl-4 item-1'],
];
It seems to do the trick, but in order to prime the script I've had to add data.children wrapper around the initial data structure. I'm not convinced it is needed, though I haven't been able to workout how to get rid of it.
Can anyone see anything I'm missing?
console.log(nestedArrayToJson(list));
function nestedArrayToJson(structure) {
const top_item = '0';
// This was added to behave like the child data structure.
let data = {
children: [
{
name: top_item,
parent: null,
children: [],
}],
};
for(let i = 0; i < structure.length; i++) {
let parents = [top_item];
for(let j = 0; j < structure[i].length; j++) {
let obj = data;
for(parent of parents) {
obj = obj.children.find(o => o.name === parent);
}
const name = structure[i][j];
if(!obj.children.find(o => o.name === name)) {
obj.children.push({
name,
parent,
children: [],
});
}
parents.push(structure[i][j]);
}
}
return data.children[0];
}
Sample Output
{
"name": "0",
"parent": null,
"children": [
{
"name": "lvl-1 item-1",
"parent": "0",
"children": [
{
"name": "lvl-2 item-1",
"parent": "lvl-1 item-1",
"children": [
{
"name": "lvl-3 item-1",
"parent": "lvl-2 item-1",
"children": []
},
{
"name": "lvl-3 item-2",
"parent": "lvl-2 item-1",
"children": []
}
]
}
]
},
{
"name": "lvl-1 item-2",
"parent": "0",
"children": [
{
"name": "lvl-2 item-1",
"parent": "lvl-1 item-2",
"children": [
{
"name": "lvl-3 item-1",
"parent": "lvl-2 item-1",
"children": []
}
]
},
{
"name": "lvl-2 item-2",
"parent": "lvl-1 item-2",
"children": [
{
"name": "lvl-3 item-2",
"parent": "lvl-2 item-2",
"children": [
{
"name": "lvl-4 item-1",
"parent": "lvl-3 item-2",
"children": []
}
]
}
]
}
]
}
]
}
The for loops can be cleaned up by extracting some functionality to named functions.
const node = (name, parent = null) => ({name, parent, children: []}) handles creating a node.
Nodes can then be added with addNode()
To search for the current next parent node findNamedNode()
If a node with the current name is found it moves down to the next node. If no node exists with the current name it is created.
function createTree(arr, topItem = 'Top') {
const node = (name, parent = null) => ({name, parent, children: []});
const addNode = (parent, child) => {
parent.children.push(child);
return child;
};
const findNamedNode = (name, parent) => {
for(const child of parent.children) {
if(child.name === name) { return child; }
const found = findNamedNode(name, child);
if(found) { return found; }
}
};
const top = node(topItem);
let current;
for(const children of arr) {
current = top;
for(const name of children) {
const found = findNamedNode(name, current);
current = found ? found : addNode(current,
node(name, current.name));
}
}
return top;
}
Thanks to the help from #Blindman67 on Code Review.
https://codereview.stackexchange.com/questions/219418/convert-nested-array-of-values-to-a-tree-structure/
This is my array of objects:
I am using vue.js , I need a tree like this to keep the structure of tree view: https://v2.vuejs.org/v2/examples/tree-view.html
[
{
"name": "",
"children": []
},
{
"name": "",
"children": [
{
"name": "Leggi",
"children": []
}
]
},
{
"name": "",
"children": [
{
"name": "Leggi",
"children": [
{
"name": "2010",
"children": []
}
]
}
]
},
{
"name": "",
"children": [
{
"name": "Leggi",
"children": [
{
"name": "2011",
"children": []
}
]
}
]
},
{
"name": "",
"children": [
{
"name": "Titoli",
"children": []
}
]
}
]
I need a function to retrive an object grouped by name with his childrens
{
"name": "",
"children": [
{
"name": "Leggi",
"children": [
{
"name": "2010",
"children": []
},
{
"name": "2011",
"children": []
}
],
"name": "Titoli",
"children": []
}
]
}
I would like to know if there it is a simple way (instead of writing a recursive function), like using lodash or something near it.
Thanks
I think that i have implemented a more readable answer:
const rootTree = [];
const putInTree = (tree, node) => {
let nodeInTree = tree.find(x => x.name === node.name);
if (!nodeInTree) {
nodeInTree = {name: node.name, children: []};
tree.push(nodeInTree);
}
if (node.children[0]) putInTree(nodeInTree.children, node.children[0])
}
nodes.forEach(node => putInTree(rootTree, node));
nodes here is your start array, let me know if this is ok
treeArchive.forEach(element => {
element.children.forEach(father => {
if (result.children.length != 0) {
cicleChildrens(result, father);
function cicleChildrens(padrePrecedente, nuovoPadre){
var brother = padrePrecedente.children.find(x => x.name == nuovoPadre.name);
if (brother != undefined) cicleChildrens(brother, nuovoPadre.children[0]);
else padrePrecedente.children.push(nuovoPadre);
};
}
else result.children.push(father);
});
});
This is currently my working code.. I'm struggling tryng to understand your code #chriss
Try this one:
function getGroupedByName(given) {
let result = given.reduce((a, b) => {
if(!a[b.name]) a[b.name] = [];
a[b.name] = [...a[b.name], ...b.children];
return a;
}, {});
result = Object.keys(result).map(key => ({name: key, children: getByName(result[key])}));
return result;
}
const o = []; // your initial object
getGroupedByName(o, "Leggi")
It is returning it as an array of objects having name and children props, as i am assuming first level can also have multiple different names, not all being ""
It goes first trough all elements in array and groups them into object with structure { name: children } where children is array of all children for same group.
For each children array it preforms same operation, going trough array and flattening it into { name: children } object.
At this moment we have following structure:
{ "": {
Leggi: {...}
}}
When everything is grouped, Object.keys loops trough all keys and breaks it into array where key is name and value children property
I'm trying to change the structure of a json by removing duplicate keys. Otherwise, to put the children of a same name inside only one name node.
Current JSON:
{
"name": "flare",
"children": [
{
"name": "analytics",
"children": [
{
"name": "cluster",
"children": [
{
"name": "AgglomerativeCluster",
"size": [
"3938"
]
}
]
}
]
},
{
"name": "analytics",
"children": [
{
"name": "cluster",
"children": [
{
"name": "CommunityStructure",
"size": [
"3812"
]
}
]
}
]
}
]
}
Desired output:
{
"name": "flare",
"children": [
{
"name": "analytics",
"children": [
{
"name": "cluster",
"children": [
{
"name": "AgglomerativeCluster",
"size": 3938
},
{
"name": "CommunityStructure",
"size": 3812
}
]
}
]
}
]
};
Thanks for your help.
Typically, StackOverflow isn't the place to have people write code for you, and your question should be more specific as to with what part of your algorithm you are having trouble. However, this looked fun, so I did it.
I solved this by first converting it to an object whose properties are the names and values are the children/size. This insured that each named instance was grouped with other named instances.
var mutate = function(desired, current) {
for (var x = 0; x < current.length; x++) {
if (Object.hasOwnProperty.call(current[x], 'size')) {
desired[current[x].name] = parseInt(current[x].size[0], 10);
}
else {
if (!Object.hasOwnProperty.call(desired, current[x].name)) {
desired[current[x].name] = Object.create(null);
}
mutate(desired[current[x].name], current[x].children);
}
}
return desired;
};
I then converted that back to your original desired format by iterating over the Object.entries (key/value pairs).
var mutate2 = function(current) {
var desired = [];
var entries = Object.entries(current);
for (var x = 0; x < entries.length; x++) {
var o = Object.create(null);
o.name = entries[x][0];
if (typeof entries[x][1] === 'number') {
o.size = entries[x][1];
}
else {
o.children = mutate2(entries[x][1]);
}
desired.push(o);
}
return desired;
};
You get your result by using this hideous beast:
var desiredJson = mutate2(mutate(Object.create(null), [ currentJson ]));
console.log(desiredJson);