Today I was working on a problem, which states as follows:
Problem:
INPUT: [{..}, {..}, ..] Array of objects;
Each object is has {"id": required, "children": []}
The objects has parent-child relation based on "id" and "children" props
OUTPUT: [{..}, {..}, ..] Array in a tree (hierarchy) order :multi-level.
Input:
[{
"id": 1,
"name": "Earth",
"children": [2, 3]
}, {
"id": 2,
"name": "Asia",
"children": []
}, {
"id": 3,
"name": "Europe",
"children": [4]
}, {
"id": 4,
"name": "Germany",
"children": [5]
}, {
"id": 5,
"name": "Hamburg",
"children": []
}]
OutPut
[{
"id": 1,
"name": "Earth",
"children": [{
"id": 2,
"name": "Asia",
"children": []
}, {
"id": 3,
"name": "Europe",
"children": [{
"id": 4,
"name": "Germany",
"children": [{
"id": 5,
"name": "Hamburg",
"children": []
}]
}]
}]
}]
My approach
I decided to solve this by iterating through each element in the array and recursively find and append objects to children of each element.
So just to start with, I decided to have only First level children appended their respective parents. And my code is following.
var posts = [{"id":1,"name":"Earth","children":[2,3]},{"id":2,"name":"Asia","children":[]},{"id":3,"name":"Europe","children":[4]},{"id":4,"name":"Germany","children":[5]},{"id":5,"name":"Hamburg","children":[]}]
function getElementById (id, posts) {
for(var i =0; i< posts.length; i++){
if(posts[i].id === id){
var found = posts[i];
///// FUN here -> //// posts.splice(i, 1);
return found;
}
}
}
function refactorChildren(element, posts) {
if(!element.children || element.children.length === 0) {
return element;
}
var children = [];
for(var i = 0; i < element.children.length; i++){
var childElement = getElementById(element.children[i], posts);
children.push(childElement);
}
element.children = children;
return element;
}
function iterate(posts) {
var newPosts = [];
var des = [...posts]
for(var i = 0; i < des.length; i++){
var childedElement = refactorChildren(des[i], des);
newPosts.push(childedElement);
}
return newPosts;
}
var filtered = iterate(posts);
console.log(JSON.stringify(filtered))
Surprisingly above code Solves the ACTUAL PROBLEM (except a lil bit of more work)
My Expected Result should be the following: Array of objects with only First level children
[{
"id": 1,
"name": "Earth",
"children": [{
"id": 2,
"name": "Asia",
"children": []
}, {
"id": 3,
"name": "Europe",
"children": [4]
}]
}, {
"id": 4,
"name": "Germany",
"children": [{
"id": 5,
"name": "Hamburg",
"children": []
}]
}]
And I do get the above result if I uncomment the ///// FUN here -> //// line. Which is erasing the iterating object on the go.
So my problem is
I want to know - HOW DID? All the objects got appended correctly to their respective Parent objects by that code? My next step was to add a recursion call to the function refactorChildren(with-childElement).
AND
How did, just by adding posts.splice(i, 1); got me MY expected result from the code?
Please help me understand, I just cant go ahead without knowing "HOW".
Thanks
While traversing the objects, you recursively call a function on all its chilfren and remove the objects from the array:
[
{ id: 1, children: [2], }, // < iterator
{ id: 2, children: [] }, // < gets spliced out recursively
]
If a child is in the array before its parent however, this won't work as you copy the child into another array before the parent gets visited.
Maybe you are interested in a different approach with only a single loop for getting the parent elements and their children.
This works for unsorted data, too.
var data = [{ id: 1, name: "Earth", children: [2, 3] }, { id: 2, name: "Asia", children: [] }, { id: 3, name: "Europe", children: [4] }, { id: 4, name: "Germany", children: [5] }, { id: 5, name: "Hamburg", children: [] }],
tree = function (array) {
var r = {},
children = new Set,
result = [];
array.forEach(o => {
Object.assign(
r[o.id] = r[o.id] || {},
o,
{ children: o.children.map(id => (children.add(id), r[id] = r[id] || {})) }
);
});
return Object.values(r).filter(({ id }) => !children.has(id));
}(data);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Related
{
"id": "1",
"name": "root",
"children": [
{
"id": "1.1",
"name": "Child 1",
"children": [
{
"id": "1.1.1",
"name": "Child 1-1",
"children": [
{
"id": "1-1-1",
"name": "Child 1-1-1",
"children": [
]
}
]
},
{
"id": "1.1.2",
"name": "Child 1-2",
"children": [
]
},
{
"id": "1.1.3",
"name": "Child 1-3",
"children": [
]
}
]
},
{
"id": "1.2",
"name": "Child 2",
"children": [
{
"id": "1.2.1",
"name": "Child 2-2",
"children": [
]
}
]
}
]
}
This is the JSON string as the response. Had to fetch for the parent elements recursively all the parents id.
For instance, input is 1.2.1 then it returns [1.2]
input is 1.1.3 then it returns [1.1, 1]
How can I achieve this?
Just a simple recursion using dfs
const data = JSON.parse('{"id":"1","name":"root","children":[{"id":"1.1","name":"Child 1","children":[{"id":"1.1.1","name":"Child 1-1","children":[{"id":"1-1-1","name":"Child 1-1-1","children":[]}]},{"id":"1.1.2","name":"Child 1-2","children":[]},{"id":"1.1.3","name":"Child 1-3","children":[]}]},{"id":"1.2","name":"Child 2","children":[{"id":"1.2.1","name":"Child 2-2","children":[]}]}]}')
function dfs (target, node) {
if (node.id === target) { return node.id }
if (!node.children) { return false } // we could even skip that line since in your data you seem to have an empty array
const has = node.children.find(c => dfs(target, c))
return has && [node.id].concat(has.id)
}
console.log(dfs('1.2.1', data))
console.log(dfs('1.1.3', data))
You could take an iterative and recursive approach and
chcek if the given data is not an object, then return
check for the id and if found return an empty array, which gets filled by the calling function
iterate children with a short circuit on found and call the function again.
function getParentIds(object, id) {
var ids;
if (!object || typeof object !== 'object') return; // no object
if (object.id === id) return []; // id found
return object.children.some(o => ids = getParentIds(o, id)) // call recursive function
? [...ids, object.id] // if found, take ids
: undefined; // otherwise return falsy
}
var data = { id: "1", name: "root", children: [{ id: "1.1", name: "Child 1", children: [{ id: "1.1.1", name: "Child 1-1", children: [{ id: "1-1-1", name: "Child 1-1-1", children: [] }] }, { id: "1.1.2", name: "Child 1-2", children: [] }, { id: "1.1.3", name: "Child 1-3", children: [] }] }, { id: "1.2", name: "Child 2", children: [{ id: "1.2.1", name: "Child 2-2", children: [] }] }] };
console.log(getParentIds(data, '1.2.1')); // ['1.2']
console.log(getParentIds(data, '1.1.3')); // ['1.1', '1']
console.log(getParentIds(data, 'foo')); // undefined
.as-console-wrapper { max-height: 100% !important; top: 0; }
You would have to parse the complete object to recursively inject the parent ids in child objects. Something like this will do the thing.
function injectParents(data, parents) {
if(!parents) {
parents = [];
}
parents.push(data.id);
data.children.map(child=>{
child.parents = parents;
if(child.children && child.children.length>0) {
child = injectParents(child, Array.from(parents));
}
return child;
});
return data;
}
Then you would normally call it like
const injectedResponse = injectParents(response, null);
you can use
for(let x in arrayName){
//here you can access the json
console.log(arayName(x).id)
}
Is this what are you looking for?
//Input
let input = '1.2.1';
//Data
data = JSON.parse('{"id":"1","name":"root","children":[{"id":"1.1","name":"Child 1","children":[{"id":"1.1.1","name":"Child 1-1","children":[{"id":"1-1-1","name":"Child 1-1-1","children":[]}]},{"id":"1.1.2","name":"Child 1-2","children":[]},{"id":"1.1.3","name":"Child 1-3","children":[]}]},{"id":"1.2","name":"Child 2","children":[{"id":"1.2.1","name":"Child 2-2","children":[]}]}]}')
//Main function start here
function findIdWhereChild(data, input) {
if (data.children?.find(x => x.id == input)) {
return data.id;
}
else {
if (data.children.length > 0) {
for (let i = 0; i < data.children.length; i++) {
let findalResult = findIdWhereChild(data.children[i], input);
if (findalResult) {
return findalResult;
};
};
} else {
return undefined;
}
}
};
//Execution LOGs
console.log(findIdWhereChild(data, input));
I have an array of data that looks like this:
{
"data": [
{
"id": "20200722_3",
"eventDate": "2020-07-22T00:00:00",
"eventName": "Football",
"eventDetails": [
"Men's First Round (2 matches)"
],
"eventVenue": "Venue A"
},
{
"id": "20200722_1",
"eventDate": "2020-07-22T00:00:00",
"eventName": "Football",
"eventDetails": [
"Men's First Round (2 matches)"
],
"eventVenue": "Venue B"
}
]
}
Now I wanted to group the data by multiple properties. For example, by eventDate, eventName, eventDetails, and eventVenue. Which I've done with this code referenced from this post:
const groupBy = (array, attrs) => {
var output = [];
for (var i = 0; i < array.length; ++i) {
var ele = array[i];
var groups = output;
for (var j = 0; j < attrs.length; ++j) {
var attr = attrs[j];
var value = ele[attr];
var gs = groups.filter(g => {
return g.hasOwnProperty('label') && g['label'] === value;
});
if (gs.length === 0) {
var g = {};
if (isArray.g['label'] ) {
}
g['label'] = value;
g['groups'] = [];
groups.push(g);
groups = g['groups'];
} else {
groups = gs[0]['groups'];
}
}
groups.push(ele);
}
return output;
}
var result = groupBy(data, ['eventDate', 'eventName', 'eventDetails', 'eventVenue'])
Which results in an array like this:
[{
"label": "2020-07-23T00:00:00",
"groups": [{
"label": "Football",
"groups": [{
"label": [
"Men's First Round (2 matches)"
],
"groups": [{
"label": "Venue A",
"groups": [
"Object"
]
}]
},
{
"label": [
"Men's First Round (2 matches)"
],
"groups": [{
"label": "Venue B",
"groups": [
"Object"
]
}]
}
}]
}]
}]
You can see that for the output above, there are two separate "groups" that have the label "Men's First Round (2 matches)". I'm trying to figure out how I can combine these objects that have duplicate value ? I'm looking for something that would output like this:
[{
"label": "2020-07-23T00:00:00",
"groups": [{
"label": "Football",
"groups": [{
"label": [
"Men's First Round (2 matches)"
],
"groups": [{
"label": "Venue A",
"groups": [
"Object"
]
},
{
"label": "Venue B",
"groups": [
"Object"
]
}]
}]
}]
}]
Any help would be greatly appreciated.
I'll share the answer that I came up with for those that are curious.
For my needs, I know that if the array with the eventName only contained 1 attribute, it could be a duplicate. So in order to fix that, I converted the array that only had 1 value to a string:
if (ele[attr] && ele[attr].length === 1) {
var value = ele[attr].toString();
} else {
var value = ele[attr];
}
So i have an array which stores hobbies for each user in an array within the object..
var hobbies = [
{
"id": 1,
"hobbies": []
},
{
"id": 2,
"hobbies": [
"football"
]
},
{
"id": 3,
"hobbies": [
"football",
"basketball"
]
}
]
What i want to return is a new array of objects but each hobby separated into their own object like below.
var result = [
{
"id": 2,
"hobby": "football"
},
{
"id": 3,
"hobby": "football"
},
{
"id": 3,
"hobby": "basketball"
}
]
What is have so far is
hobbies.filter((f, i) => f.hobbies.length > 0).map((p, i) => {
while (i < p.hobbies.length) {
return { id : p.id, hobby : p.hobbies[i] };
}
});
which only returns
[
{
"id": 2,
"hobby": "football"
},
{
"id": 3,
"hobby": "basketball"
}
]
You can use array#reduce with array#map. Iterate through each object and then iterate through each hobby of hobbies and create the object.
var hobbies = [ { "id": 1, "hobbies": [] }, { "id": 2, "hobbies": [ "football" ] }, { "id": 3, "hobbies": [ "football", "basketball" ] } ],
result = hobbies.reduce((r, {id, hobbies}) => r.concat(hobbies.map(hobby => ({id, hobby}))), []);
console.log(result);
I know, "functional" programming is considered "cool" around these parts, however, have you considered using simple loops to, well, loop over your data?
let result = [];
for (let {hobbies, id} of data)
for (let hobby of hobbies)
result.push({id, hobby})
In my opinion, this is far more readable than any reduce spaghetti one could come up with ;)
You need to use inner-loop to loop through the hobbies and push them one-by-one to the target array:
var hobbies = [{
"id": 1,
"hobbies": []
},
{
"id": 2,
"hobbies": [
"football"
]
},
{
"id": 3,
"hobbies": [
"football",
"basketball"
]
}
];
var result = hobbies.reduce((acc, item) => {
item.hobbies.forEach(hobby => {
acc.push({
id: item.id,
hobby: hobby
});
});
return acc;
}, []);
console.log(result);
You can use array.prototype.reduce:
var hobbies = [{"id": 1,"hobbies": []},{"id": 2,"hobbies": ["football"]},{"id": 3, "hobbies": ["football","basketball"]}];
var res = hobbies.reduce((m, o) => (o.hobbies.forEach(h => m.push({id: o.id, hobby: h})), m), []);
console.log(res);
You need nested loops and this is the basics of it:
You first need to loop over the main hobbies array.
Then for each item in the array (which represents a person), you want to loop through their hobbies, and for each one of those hobbies, you need to push an object made up of the profile ID and the hobby into results array I created earlier.
var hobbies = [{ "id": 1, "hobbies": [] }, { "id": 2, "hobbies": [ "football" ] }, { "id": 3, "hobbies": [ "football", "basketball" ] } ];
let result = [];
hobbies.forEach(function(profile){
profile.hobbies.forEach(function(hobby){
result.push(
{
"id": profile.id,
"hobby": hobby
}
);
});
});
console.log(result)
Update: the other answers with Array.reduce (a more specialised loop) will cut the above code down even further.
Suppose I have a tree of objects like the following, perhaps created using the excellent algorithm found here: https://stackoverflow.com/a/22367819/3123195
{
"children": [{
"id": 1,
"title": "home",
"parent": null,
"children": []
}, {
"id": 2,
"title": "about",
"parent": null,
"children": [{
"id": 3,
"title": "team",
"parent": 2,
"children": []
}, {
"id": 4,
"title": "company",
"parent": 2,
"children": []
}]
}]
}
(Specifically in this example, the array returned by that function is nested as the children array property inside an otherwise empty object.)
How would I convert it back to a flat array?
Hope your are familiar with es6:
let flatten = (children, extractChildren) => Array.prototype.concat.apply(
children,
children.map(x => flatten(extractChildren(x) || [], extractChildren))
);
let extractChildren = x => x.children;
let flat = flatten(extractChildren(treeStructure), extractChildren)
.map(x => delete x.children && x);
UPD:
Sorry, haven't noticed that you need to set parent and level. Please find the new function below:
let flatten = (children, getChildren, level, parent) => Array.prototype.concat.apply(
children.map(x => ({ ...x, level: level || 1, parent: parent || null })),
children.map(x => flatten(getChildren(x) || [], getChildren, (level || 1) + 1, x.id))
);
https://jsbin.com/socono/edit?js,console
This function will do the job, plus it adds a level indicator to each object. Immediate children of treeObj will be level 1, their children will be level 2, etc. The parent properties are updated as well.
function flatten(treeObj, idAttr, parentAttr, childrenAttr, levelAttr) {
if (!idAttr) idAttr = 'id';
if (!parentAttr) parentAttr = 'parent';
if (!childrenAttr) childrenAttr = 'children';
if (!levelAttr) levelAttr = 'level';
function flattenChild(childObj, parentId, level) {
var array = [];
var childCopy = angular.extend({}, childObj);
childCopy[levelAttr] = level;
childCopy[parentAttr] = parentId;
delete childCopy[childrenAttr];
array.push(childCopy);
array = array.concat(processChildren(childObj, level));
return array;
};
function processChildren(obj, level) {
if (!level) level = 0;
var array = [];
obj[childrenAttr].forEach(function(childObj) {
array = array.concat(flattenChild(childObj, obj[idAttr], level+1));
});
return array;
};
var result = processChildren(treeObj);
return result;
};
This solution takes advantage of Angular's angular.extend() function to perform a copy of the child object. Wiring this up with any other library's equivalent method or a native function should be a trivial change.
The output given for the above example would be:
[{
"id": 1,
"title": "home",
"parent": null,
"level": 1
}, {
"id": 2,
"title": "about",
"parent": null,
"level": 1
}, {
"id": 3,
"title": "team",
"parent": 2,
"level": 2
}, {
"id": 4,
"title": "company",
"parent": 2,
"level": 2
}]
It is also worth noting that this function does not guarantee the array will be ordered by id; it will be based on the order in which the individual objects were encountered during the operation.
Fiddle!
Here it goes my contribution:
function flatNestedList(nestedList, childrenName, parentPropertyName, idName, newFlatList, parentId) {
if (newFlatList.length === 0)
newFlatList = [];
$.each(nestedList, function (i, item) {
item[parentPropertyName] = parentId;
newFlatList.push(item);
if (item[childrenName] && item[childrenName].length > 0) {
//each level
flatNestedList(item[childrenName], childrenName, parentPropertyName, idName, newFlatList, item[idName]);
}
});
for (var i in newFlatList)
delete (newFlatList[i][childrenName]);
}
Try following this only assumes each item is having children property
class TreeStructureHelper {
public toArray(nodes: any[], arr: any[]) {
if (!nodes) {
return [];
}
if (!arr) {
arr = [];
}
for (var i = 0; i < nodes.length; i++) {
arr.push(nodes[i]);
this.toArray(nodes[i].children, arr);
}
return arr;
}
}
Usage
let treeNode =
{
children: [{
id: 1,
title: "home",
parent: null,
children: []
}, {
id: 2,
title: "about",
parent: null,
children: [{
id: 3,
title: "team",
parent: 2,
children: []
}, {
id: 4,
title: "company",
parent: 2,
children: []
}]
}]
};
let flattenArray = _treeStructureHelper.toArray([treeNode], []);
This is data:
const data = {
id: '1',
children: [
{
id: '2',
children: [
{
id: '4',
children: [
{
id: '5'
},
{
id: '6'
}
]
},
{
id: '7'
}
]
},
{
id: '3',
children: [
{
id: '8'
},
{
id: '9'
}
]
}
]
}
In React.JS just declare an array field in state and push items to that array.
const getAllItemsPerChildren = item => {
array.push(item);
if (item.children) {
return item.children.map(i => getAllItemsPerChildren(i));
}
}
After function call your array in state will hold all items as below:
One more 😄😁
function flatten(root, parent=null, depth=0, key='id', flat=[], pick=() => {}) {
flat.push({
parent,
[key]: root[key],
depth: depth++,
...pick(root, parent, depth, key, flat)
});
if(Array.isArray(root.children)) {
root.children.forEach(child => flatten(child, root[key], depth, key, flat, pick));
}
}
let sample = {
"id": 0,
"children": [{
"id": 1,
"title": "home",
"parent": null,
"children": []
}, {
"id": 2,
"title": "about",
"parent": null,
"children": [{
"id": 3,
"title": "team",
"parent": 2,
"children": []
}, {
"id": 4,
"title": "company",
"parent": 2,
"children": []
}]
}]
};
let flat = [];
flatten(sample, null, 0, 'id', flat, root => ({ title: root.title }));
let expected = [
{
"id": 0,
"parent": null,
"depth": 0
},
{
"id": 1,
"parent": 0,
"depth": 1,
"title": "home"
},
{
"id": 2,
"parent": 0,
"depth": 1,
"title": "about"
},
{
"id": 3,
"parent": 2,
"depth": 2,
"title": "team"
},
{
"id": 4,
"parent": 2,
"depth": 2,
"title": "company"
}
];
I am a little stuck on this. I want to create a tree structure from a flat array. Say I have this input:
var input = [
["a","b","c"],
["a", "b","d"],
["e","f","g"],
];
I want to create a tree structure looking like the following:
// output:
[
{
id: "a",
children: [
{id: "b", children: [
{id: "c", children: []},
{id: "d", children: []}
]},
]
},
{
id: "e",
children: [
{
id: "f",
children: [{ id: "g", children: []}]
},
]
}
]
One way I was thinking of doing this was having a map of all of the parent and iterate through the input array to set the parent to child mappings. But I come to problems when trying to actually construct the tree object from that map and avoiding duplicates. Any pointers are appreciated, thanks!
I found the solution to a problem that is similar to your question.
_makeTree
If you have data that looks like this:
_makeTree({ q:
[
{"id": 123, "parentid": 0, "name": "Mammals"},
{"id": 456, "parentid": 123, "name": "Dogs"},
{"id": 214, "parentid": 456, "name": "Labradors"},
{"id": 810, "parentid": 456, "name": "Pugs"},
{"id": 919, "parentid": 456, "name": "Terriers"}
]
});
Parameters:
q (Array): A query result (see example below)
id (String): The name of the id column (Default: "id")
parentid (String): The name of the ParentItemID column (Default: "parentid")
children (String): The name of the "children" array to be created in rows that have children (Default: "children")
Then result should be something like the following structure:
[
{
"id": 123,
"parentid": 0,
"name": "Mammals",
"children": [
{
"id": 456,
"parentid": 123,
"name": "Dogs",
"children": [
{
"id": 214,
"parentid": 456,
"name": "Labradors"
},
{
"id": 810,
"parentid": 456,
"name": "Pugs"
},
{
"id": 919,
"parentid": 456,
"name": "Terriers"
}
]
}
]
}
]
Now, _makeTree codes:
var _makeTree = function(options) {
var children, e, id, o, pid, temp, _i, _len, _ref;
id = options.id || "id";
pid = options.parentid || "parentid";
children = options.children || "children";
temp = {};
o = [];
_ref = options.q;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
e = _ref[_i];
temp[e[id]] = e;
if (temp[e[pid]] != null) {
if (temp[e[pid]][children] == null) {
temp[e[pid]][children] = [];
}
temp[e[pid]][children].push(e);
} else {
o.push(e);
}
}
return o;
};
References:
I need to create a custom tree data-structure using JavaScript
Creating trees from SQL queries in Javascript
_makeTree library