Array transformation tree structure - javascript

I have an array that needs to be converted to a tree structure, and I've searched for some scenarios, such as the following code, but once the amount of data is too large, the time required to generate this tree structure seems too long, ask if there can be a better solution or optimization!
var data = [{ id: 1, name: 'A', parentId: 0 },{ id: 2, name: 'B', parentId: 1 },{ id: 3, name: 'C', parentId: 2 },{ id: 4, name: 'D', parentId: 3 },{ id: 5, name: 'E', parentId: 4 },{ id: 6, name: 'F', parentId: 5 },{ id: 7, name: 'G', parentId: 6 },{ id: 8, name: 'H', parentId: 7 },{ id: 9, name: 'Z', parentId: 8 }];
var toTree = function(data) {
var map = {};
data.forEach(function(item) {
map[item.id] = item;
});
var val = [];
data.forEach(function(item) {
var parent = map[item.parentId];
if (parent) {
(parent.children || (parent.children = [])).push(item);
} else {
val.push(item);
}
});
return val;
}
console.log(toTree(data))

You could use a single loop approach where id and parentId is used for building a tree in the same loop.
At the end return the root node's children.
var data = [{ id: 1, name: 'A', parentId: 0 }, { id: 2, name: 'B', parentId: 1 }, { id: 3, name: 'C', parentId: 2 }, { id: 4, name: 'D', parentId: 3 }, { id: 5, name: 'E', parentId: 4 }, { id: 6, name: 'F', parentId: 5 }, { id: 7, name: 'G', parentId: 6 }, { id: 8, name: 'H', parentId: 7 }, { id: 9, name: 'Z', parentId: 8 }],
tree = function (data, root) {
var o = {};
data.forEach(function (a) {
a.children = o[a.id] && o[a.id].children;
o[a.id] = a;
o[a.parentId] = o[a.parentId] || {};
o[a.parentId].children = o[a.parentId].children || [];
o[a.parentId].children.push(a);
});
return o[root].children;
}(data, 0);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

One approach is to change your data in the first place, so that it does not come from an array but a tree structure map and data map:
var treeStructure = {
'ROOT': ['childId1', 'childId2'],
'childId1': ['childId3']
}
var data = {
'childId1': {
'property1':1
},
'childId2': {
'property1':2
},
'childId3': {
'property1': 3
}
}
This is so that the relationship of parent-child will be very clear and it will be very easy to find each item.

Related

Move items to front of array if they exists in another array, based on key

I'm sort an array based on the keys in another array. If they find a match, it would move those items to the front of the array. But I can't think of a clean way to do this.
let myArray = [
{ id: 'a', name: 'Mal' },
{ id: 'b', name: 'Wash'},
{ id: 'c', name: 'Inara'},
{ id: 'd', name: 'Jayne'},
]
let sortArray = [
{ id: 'b' },
{ id: 'c' },
{ id: 'x' },
]
/* Expected result
myArray = [
{ id: 'b', name: 'Wash'},
{ id: 'c', name: 'Inara'},
{ id: 'a', name: 'Mal' },
{ id: 'd', name: 'Jayne'},
]
/*
Does anyone know a way to do this without just looping through it a bunch of times? Thanks
You could create a Map which maps each id in sortArray to its index. Use this priority map object to sort the first array.
const array = [{ id: 'a', name: 'Mal' }, { id: 'b', name: 'Wash'}, { id: 'c', name: 'Inara'}, { id: 'd', name: 'Jayne'}],
sortArray = [{ id: 'b' }, { id: 'c' }, { id: 'x' }],
map = new Map( sortArray.map((o, i) => [o.id, i]) )
array.sort((a,b) =>
( map.has(b.id) - map.has(a.id) ) || ( map.get(a.id) - map.get(b.id) )
)
console.log(array)
You could take an object ffor the wanted order of the items and a default value for unknown items.
let array = [{ id: 'a', name: 'Mal' }, { id: 'b', name: 'Wash'}, { id: 'c', name: 'Inara'}, { id: 'd', name: 'Jayne'}],
sortArray = [{ id: 'b' }, { id: 'c' }, { id: 'x' }],
order = Object.fromEntries(sortArray.map(({ id }, i) => [id, i + 1]));
array.sort(({ id: a }, { id: b }) =>
(order[a] || Number.MAX_VALUE) - (order[b] || Number.MAX_VALUE)
);
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }

How can I create nested tree in array object

Following the array object data:
Using Javascript data structures, how could I iterate over array object into children array's, every time when I get new children using its ID and parent ID.
Trying to find Parent Id and child nested root nodes using javascript but I couldn't able find, how to iterate array object into its children array. can anyone help me out.
var data = [
{ id: 7, label: 'a', parentId: 2, childerns: [] },
{ id: 7, label: 'm', parentId: 2, childerns: [] },
{ id: 2, label: 'b', parentId: 5, childerns: [] },
{ id: 5, label: 'c', parentId: 20, childerns: [] },
{ id: 20, label: 'x', parentId: null, childerns: [] },
{ id: 8, label: 'd', parentId: 7, childerns: [] },
{ id: 9, label: 'n', parentId: 8, childerns: [] },
{ id: 9, label: 'n', parentId: 8, childerns: [] } ];```
and looking for the following nested tree pattern:
```var data = [
{
id: 20,
label: 'x',
parentId: null,
childerns: [
{
id: 5,
label: 'c',
parentId: 20,
childerns: [{
id: 2,
label: 'b',
parentId: 5,
childerns: [
{
id: 7,
label: 'm',
parentId: 2,
childerns: []
},
{
id: 7,
label: 'a',
parentId: 2,
childerns: [{
id: 8,
label: 'd',
parentId: 7,
childerns: [
{
id: 9,
label: 'n',
parentId: 8,
childerns: []
},
{
id: 9,
label: 'n',
parentId: 8,
childerns: []
}
]
}]
}]
}]
}]
}];```
I like the following pattern. First, you use an object to track where in the original array each element lives. Then, you iterate through the original array, adding a reference to the current element to its parent array. If parentId is null, add the element to roots. After all this, your roots array will contain the full tree.
const arr = [
{ id: 7, label: 'm', parentId: 2, childerns: [] },
{ id: 2, label: 'b', parentId: 5, childerns: [] },
{ id: 5, label: 'c', parentId: 20, childerns: [] },
{ id: 20, label: 'x', parentId: null, childerns: [] },
{ id: 8, label: 'd', parentId: 7, childerns: [] },
{ id: 9, label: 'n', parentId: 8, childerns: [] },
{ id: 10, label: 'n', parentId: 8, childerns: [] }
];
// Map element ID to arr index
const arrMap = arr.reduce((acc, el, i) => {
acc[el.id] = i;
return acc;
}, {});
const roots = [];
// Push each element to parent's children array
arr.forEach(el => {
if (el.parentId === null) {
roots.push(el);
} else {
arr[arrMap[el.parentId]].childerns.push(el);
}
});
console.log(roots);
If you are looking for a pattern where the children can expand almost infinitely, you should use a class or a constructor, so you can just pass another instance within, as demonstrated in the following UnitInstance.addChild method.
/* constructor */
function Unit(id, label, children){
this.id = id; this.label = label; this.parentId = null; this.children = [];
var t = this;
this.addChild = function(unitInstance){
unitInstance.parentId = this.id; this.children.push(unitInstance);
return this;
}
if(children){
children.forEach(function(o){
t.addChild(o);
});
}
this.getData = function(){
var c = [];
this.children.forEach(function(o){
c.push(o.getData());
});
return {id:t.id, label:t.label, parentId:t.parentId, children:c};
}
}
var x = new Unit(20, 'x'), c = new Unit(5, 'c'), b = new Unit(2, 'b'), m = new Unit(7, 'm'); a = new Unit(7, 'a'), d = new Unit(8, 'd'), n = new Unit(9, 'n');
x.addChild(c.addChild(b.addChild(m.addChild(a.addChild(d.addChild(n).addChild(n))))));
var data = [x.getData()];
console.log(data);

Treeview in javascript

I need to make something similar to treeview. It doesn't need collapsing it just needs to show some heirachy, but in a table view.
Flat data comes in from a database. I unflattened it and made a tree, but now that it's a tree, I wanted to turn it back into an array, so I can easily iterate using a for loop.
After looking at the source code of other treeviews my method was going to be like this:
From flat data from a db, unflatten:
[
{ id: 1, name: 'node1', parentId: 0 },
{ id: 2, name: 'node2', parentId: 1 },
{ id: 4, name: 'node4', parentId: 2 },
{ id: 5, name: 'node5', parentId: 2 },
{ id: 6, name: 'node6', parentId: 3 },
{ id: 3, name: 'node3', parentId: 1 },
]
The tree is now ordered and has a hierarchy (levels for indentation). Traverse the tree. I add level and children.
[
id: 1,
name: 'node1',
level: 0,
children: [
{
id: 2,
name: 'node2',
parentId: 1,
level: 1
children: [
{
id: 4,
name: 'node4',
parentId: 2,
level: 2,
children: []
},
{
id: 5,
name: 'node5',
parentId: 2,
children: []
},
]
},
{
id: 3,
name: 'node1',
parentId: 1,
children: [
{
id: 6,
name: 'node6',
parentId: 3,
children: []
},
]
},
]
]
Compress it back into an array form with order, level.
[
{ id: 1, name: 'node1', level: 0, parentId: 0, children: [...] },
{ id: 2, name: 'node2', level: 1, parentId: 1, children: [...] },
{ id: 4, name: 'node4', level: 2, parentId: 2, children: [...] },
{ id: 5, name: 'node5', level: 2, parentId: 2, children: [...] },
{ id: 3, name: 'node3', level: 1, parentId: 1, children: [...] },
{ id: 6, name: 'node6', level: 2, parentId: 3, children: [...] },
]
Of which I can easily create a table from.
I've gotten close with the following code:
var data = [
{ id: 1, name: 'node1', parentId: 0 },
{ id: 2, name: 'node2', parentId: 1 },
{ id: 4, name: 'node4', parentId: 2 },
{ id: 5, name: 'node5', parentId: 2 },
{ id: 6, name: 'node6', parentId: 3 },
{ id: 3, name: 'node3', parentId: 1 }
]
function unflatten (arr, parentId, level) {
let output = []
for (const obj of arr) {
if (obj.parentId === parentId) {
var children = unflatten(arr, obj.id, level+1)
obj.level = level
if (children.length) {
obj.children = children
}
output.push(obj)
}
}
// console.log(output)
return output
}
function flatten (tree) {
var output = []
for(const node of tree) {
if(node.children !== undefined){
var nodeChildren = flatten(node.children.reverse())
for(const child of nodeChildren){
output.push(child)
}
}
output.push(node)
}
return output
}
var dataCopy = Object.assign([], data)
console.log('data', dataCopy)
var res = unflatten(data, 0, 0)
console.log('tree', res)
var resCopy = Object.assign([], res)
var res2 = flatten(resCopy)
console.log('reflatten', res2)
Fiddle http://jsfiddle.net/ctd09r85/10/
That fiddle is the closest I've gotten, but it's a bit reversed and out of order.
How can I do this, and is this a reasonable way to build the tree view.

How to convert objects containing array into object of objects

My code:
function convert(arr, parent) {
var out = [];
for(var i in arr) {
if(arr[i].parent == parent) {
var children = convert(arr, arr[i].id)
if(children.length) {
arr[i].children = children
}
out.push(arr[i])
}
}
return out; //return Object.assign({}, out);tried this, but i lose parents childrens arrays
};
arras = [
{id: 1, name: "parent1", parent: null},
{id: 2, name: "children1", parent: 1},
{id: 3, name: "children2", parent: 1},
{id: 4, name: "parent2", parent: null},
{id: 5, name: "children3", parent: 4},
{id: 6, name: "children4", parent: 4}
]
console.log(convert(arras, null));
How final result should look
{
parent1: [
{name: "children1"},
{name: "children2"}
],
parent2: [
{name: "children3},
{name: "children4"}
]
}
What my output looks so far:
[
{id: 1, name: "parent1", parent: null}: [
{id: 2, name: "children1", parent: 1},
{id: 3, name: "children2", parent: 1},
],
{id: 4, name: "parent2", parent: null}: [
{id: 5, name: "children3", parent: 4},
{id: 6, name: "children4", parent: 4}
]
]
So firstly, what I have to do is convert main array to object, when I tend to do that, I lose both parent object arrays...Also need to change the way console displays objects, any help is appreciated.
You could build a tree with check if parent is a root node or not.
var data = [{ id: 1, name: "parent1", parent: null }, { id: 2, name: "children1", parent: 1 }, { id: 3, name: "children2", parent: 1 }, { id: 4, name: "parent2", parent: null }, { id: 5, name: "children3", parent: 4 }, { id: 6, name: "children4", parent: 4 }],
tree = function (data, root) {
var r = {},
o = {};
data.forEach(function (a) {
if (a.parent === root) {
r[a.name] = [];
o[a.id] = r[a.name];
} else {
o[a.parent] = o[a.parent] || [];
o[a.parent].push({ name: a.name });
}
});
return r;
}(data, null);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Try this.
function convert(arr) {
var parents = {};
for (var i in arr) {
if (arr[i].parent === null) {
parents[arr[i].id] = arr[i].name
}
}
var out = {}
for (i in arr) {
if (arr[i].parent !== null) {
var parentName = parents[arr[i].parent];
if (out.hasOwnProperty(parentName)) {
out[parentName].push(arr[i].name)
} else {
out[parentName] = [arr[i].name]
}
}
}
return out;
};
arras = [{
id: 1,
name: "parent1",
parent: null
},
{
id: 2,
name: "children1",
parent: 1
},
{
id: 3,
name: "children2",
parent: 1
},
{
id: 4,
name: "parent2",
parent: null
},
{
id: 5,
name: "children3",
parent: 4
},
{
id: 6,
name: "children4",
parent: 4
}
]
//console.log(convert(arras, null));
alert(JSON.stringify(convert(arras)));
But notice for multilevel it doesn't work correctly. If your need it, your must save map for all possible parent list
arras.forEach(function(el){
if(el.parent){
el.parent=arras.find(e=>e.id==el.parent)||(console.error("no parent:"+el.parent),undefined);
}
});
//resolved parent/childs....
var newmodel = arras.reduce(function(obj,el){
if(el.parent){
//child
obj[el.parent.name]=obj[el.parent.name]||[];//create new parent if neccessary
obj[el.parent.name].push({name:el.name});
}else{
//parent
obj[el.name]=obj[el.name]||[];
}
return obj;
},{});
http://jsbin.com/renicijufi/edit?console
Another way:
var arrays = [
{id: 1, name: 'parent1', parent: null},
{id: 2, name: 'children1', parent: 1},
{id: 3, name: 'children2', parent: 1},
{id: 4, name: 'parent2', parent: null},
{id: 5, name: 'children3', parent: 4},
{id: 6, name: 'children4', parent: 4}
];
// First, reduce the input arrays to id based map
// This step help easy to select any element by id.
arrays = arrays.reduce(function (map, el) {
map[el.id] = el;
return map;
}, {});
var result = Object.values(arrays).reduce(function (result, el) {
if (!el.parent) {
result[el.name] = [];
} else {
result[arrays[el.parent].name].push(el.name);
}
return result;
}, {});
console.log(result);
I think this meets your requirement
Obj = new Object();
for( i in arras){
person = arras[i];
if(person.parent != null){
if(!Obj.hasOwnProperty(person.parent)){
// here instead of the index you can use Obj["parent"+person.parent] get the exact thing. If you are using that use tha in rest of the code
Obj[person.parent] = new Array();
}
Obj[person.parent].push(person);
}
else{
if(!Obj.hasOwnProperty(person.id)){
// Some parents might have kids not in the list. If you want to ignore, just remove from the else.
Obj[person.id] = new Array()
}
}
}
Edit :
Obj = new Object();
for( i in arras){
person = arras[i];
if(person.parent != null){
if(!Obj.hasOwnProperty(person.parent)){
// here instead of the index you can use Obj["parent"+person.parent] get the exact thing. If you are using that use tha in rest of the code
Obj[person.parent] = new Array();
}
Obj[person.parent].push({name : person.name});
}
else{
if(!Obj.hasOwnProperty(person.id)){
// Some parents might have kids not in the list. If you want to ignore, just remove from the else.
Obj[person.id] = new Array()
}
}
}
Hope this helps. :)

How to build a tree from a flat list in FP JS

I'm learning Functional Javascript and encounter into a problem.
I have this flat object:
const data = [
{id: 1, name: "Folder1", parentId: null},
{id: 2, name: "Folder2", parentId: null},
{id: 3, name: "Folder3", parentId: 1},
{id: 4, name: "Folder4", parentId: 2},
{id: 5, name: "Folder5", parentId: 3},
{id: 6, name: "Folder6", parentId: 3}
]
I desire to convert it to this hierarchical object, using only pure functions, no fors, ifs and other "imperative style statements".
Result should be:
[{
id: 1,
name: "Folder1",
parentId: null,
children = [{
id: 3,
name: "Folder3",
parentId: 1,
children = [{
id: 5,
name: "Folder5",
parentId: 3
},
{
id: 6,
name: "Folder6",
parentId: 3
}
]
}]
},
{
id: 2,
name: "Folder2",
parentId: null,
children = [{
id: 4,
name: "Folder4",
parentId: 2
}]
}
]
Any Ideas?
This is a proposal without if, but with Array#reduce and Map. It needs a sorted array.
var data = [{ id: 1, name: "Folder1", parentId: null }, { id: 2, name: "Folder2", parentId: null }, { id: 3, name: "Folder3", parentId: 1 }, { id: 4, name: "Folder4", parentId: 2 }, { id: 5, name: "Folder5", parentId: 3 }, { id: 6, name: "Folder6", parentId: 3 }],
tree = data
.reduce(
(m, a) => (
m
.get(a.parentId)
.push(Object.assign({}, a, { children: m.set(a.id, []).get(a.id) })),
m
),
new Map([[null, []]])
)
.get(null);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Or the same as above using ES2015 destructuring assignment. It needs a sorted array and also depends on the input data having only id, name and parentId keys.
var data = [{ id: 1, name: "Folder1", parentId: null }, { id: 2, name: "Folder2", parentId: null }, { id: 3, name: "Folder3", parentId: 1 }, { id: 4, name: "Folder4", parentId: 2 }, { id: 5, name: "Folder5", parentId: 3 }, { id: 6, name: "Folder6", parentId: 3 }],
tree = data
.reduce(
(m, {id, name, parentId}) => (
m
.get(parentId)
.push({id, name, parentId, children: m.set(id, []).get(id) }),
m
),
new Map([[null, []]])
)
.get(null);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Of course this should probably be written as a reusable function ...
var data = [{ id: 1, name: "Folder1", parentId: null }, { id: 2, name: "Folder2", parentId: null }, { id: 3, name: "Folder3", parentId: 1 }, { id: 4, name: "Folder4", parentId: 2 }, { id: 5, name: "Folder5", parentId: 3 }, { id: 6, name: "Folder6", parentId: 3 }];
// pure, reusable function
var buildTree = (data) =>
data.reduce(
(m, {id, name, parentId}) => (
m
.get(parentId)
.push({id, name, parentId, children: m.set(id, []).get(id) }),
m
),
new Map([[null, []]])
)
.get(null);
console.log(buildTree(data));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Lastly, if the data is arriving in an unsorted order, we could handle sorting with a custom comparator
// unsorted data example
var data = [{ id: 6, name: "Folder6", parentId: 3 }, { id: 2, name: "Folder2", parentId: null }, { id: 3, name: "Folder3", parentId: 1 }, { id: 4, name: "Folder4", parentId: 2 }, { id: 5, name: "Folder5", parentId: 3 }, { id: 1, name: "Folder1", parentId: null }];
// immutable sort
var sort = (f,xs) => [...xs.sort(f)];
// custom tree comparator
var treeComparator = (x,y) =>
x.parentId - y.parentId || x.id - y.id;
// sort data, then reduce
var buildTree = (data) =>
sort(treeComparator, data).reduce(
(m, {id, name, parentId}) => (
m
.get(parentId)
.push({id, name, parentId, children: m.set(id, []).get(id) }),
m
),
new Map([[null, []]])
)
.get(null);
console.log(buildTree(data));
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can do this with recursive function but you need to loop array with reduce and use if statements.
const arr = [
{id: 1, name: "Folder1", parentId: null},
{id: 2, name: "Folder2", parentId: null},
{id: 3, name: "Folder3", parentId: 1},
{id: 4, name: "Folder4", parentId: 2},
{id: 5, name: "Folder5", parentId: 3},
{id: 6, name: "Folder6", parentId: 3}
]
function buildTree(data, pId) {
return data.reduce(function(r, e) {
var e = Object.assign({}, e);
if (e.parentId == pId) {
var children = buildTree(data, e.id)
if (children.length) e.children = children
r.push(e)
}
return r;
}, [])
}
console.log(buildTree(arr, null))
const data = [
{id: 1, name: "Folder1", parentId: null},
{id: 2, name: "Folder2", parentId: null},
{id: 3, name: "Folder3", parentId: 1},
{id: 4, name: "Folder4", parentId: 2},
{id: 5, name: "Folder5", parentId: 3},
{id: 6, name: "Folder6", parentId: 3}
];
function trampoline ( f ) {
while ( f && f instanceof Function ) { f = f ( ); }
return f;
}
function buildTree ( data, copy, top = [] ) {
function recur ( data, copy, top ) {
copy = copy || data.concat ( [] );
let current = copy.shift ( );
current ? doWork ( ) : null;
function doWork ( ) {
top = top.concat ( ( ! current.parentId ? current : [] ) );
current.children = copy.filter ( x => { return current.id === x.parentId } );
}
return ( current ? recur.bind ( null, data, copy, top ) : top );
}
return trampoline ( recur.bind ( null, data, copy, top ) );
}
data.map ( x => { x [ 'children' ] = [ ]; return x; } );
console.log ( buildTree ( data ) );

Categories