Sorting an array with child - parent dependencies - javascript

I am given the following array of objects:
let data = [{id: 3, name: ''}, {id: 4, name: ''}, {id: 5, name: '', parent: 3}, {id: 6, name: '', parent: 5}, {id: 7, name: '', parent: 4}, {id: 8, name: '', parent: 8}];
How do I create a nested list, where an object with corresponding parent element is appended to a new <li> element? So to make it clear it would look something like this:
<li>
object id:3 (parent)
<li>object id:5</li>(child)
</li>`
Any clues? :)

You could do this with two functions one to create nested tree structure and another one to create a nested html structure based on previously create tree structure.
let data = [{id: 3, name: ''}, {id: 4, name: ''}, {id: 5, name: '', parent: 3}, {id: 6, name: '', parent: 5}, {id: 7, name: '', parent: 4}, {id: 8, name: '', parent: 8}];
const toTree = (data, pid = undefined) => {
return data.reduce((r, e) => {
if (pid == e.parent) {
const obj = { ...e }
const children = toTree(data, e.id);
if (children) obj.children = children
r.push(obj)
}
return r
}, [])
}
const toHtml = (data) => {
const ul = document.createElement('ul')
data.forEach(e => {
const li = document.createElement('li');
const text = document.createElement('span')
text.textContent = `object id: ${e.id}`;
li.appendChild(text)
const children = toHtml(e.children);
if (children) li.appendChild(children)
ul.appendChild(li)
})
return ul
}
const tree = toTree(data)
const html = toHtml(tree)
document.body.appendChild(html)

Related

array grouping conversion key start_section to end_section

Let's say I have below array :
[{id: 1, name: "header"},{id: 2, name: "start_section"},
{id: 3, name: "input"}, {id: 5, name: "image"},
{id: 6, name: "end_section"}, {id: 7, name: "header"},
{id: 8, name: "start_section"}, {id: 9, name: "input"},
{id: 10, name: "date"}, {id: 11, name: "end_section"},
]
I want this :
[{
id: 1,
name: "header"
}, {
id: 2,
name: "section",
child: [{
{
id: 3,
name: "input"
},
{
id: 5,
name: "image"
},
}],
}, {
id: 7,
name: "header"
}, {
id: 8,
name: "section",
child: [{
{
id: 9,
name: "input"
},
{
id: 10,
name: "date"
},
}]
}]
if I find start_section and end_section then it will form a new object , How do I change the array by grouping by the key specified in the example above in javascript?
If I get it right, you want something like this? It's simple approach with for loop and some flags:
const arr = [{id: 1, name: "header"},{id: 2, name: "start_section"},
{id: 3, name: "input"}, {id: 5, name: "image"},
{id: 6, name: "end_section"}, {id: 7, name: "header"},
{id: 8, name: "start_section"}, {id: 9, name: "input"},
{id: 10, name: "date"}, {id: 11, name: "end_section"},
];
// Set final array
let finalArray = [];
// Set sub object for groups (Childs)
let subObj = {};
// Flag for sub section stuff
let inSubSection = false;
// Loop array
for(let i = 0; i < arr.length; i++) {
if(arr[i].name === "end_section") {
// If we have end_section
// Set flag off
inSubSection = false;
// Push sub object to final array
finalArray.push(subObj);
} else if(arr[i].name === "start_section") {
// If we get start_section
// Set flag on
inSubSection = true;
// Set new sub object, set childs array in it
subObj = {
id: arr[i].id,
name: "section",
child: []
};
} else if(inSubSection) {
// If we have active flag (true)
// Push child to section array
subObj.child.push({
id: arr[i].id,
name: arr[i].name
});
} else {
// Everything else push straight to final array
finalArray.push(arr[i]);
}
}
// Log
console.log(finalArray);
you can Array.reduce function
let array = [{id: 1, name: "header"},{id: 2, name: "start_section"},
{id: 3, name: "input"}, {id: 5, name: "image"},
{id: 6, name: "end_section"}, {id: 7, name: "header"},
{id: 8, name: "start_section"}, {id: 9, name: "input"},
{id: 10, name: "date"}, {id: 11, name: "end_section"},
]
let outPut = array.reduce( (acc, cur, i, arr) => {
if (cur.name == "start_section") {
//find the end element
let endIndex = arr.slice(i).findIndex( e => e.name == "end_section") + i ;
//splice the child elements from base array
let child = arr.splice(i + 1, endIndex - 1 );
//remove last element that has "end_section"
child.splice(-1);
//append child
cur.child = child;
//sert the name as "section"
cur.name = "section";
}
//add to accumulator
acc.push(cur);
return acc;
}, []);
console.log(outPut);

Turn array of objects into structured object? [duplicate]

I am working on a Nodejs project. I have to create a function which takes an object (a child category) like:
{
id: 65,
name: 'Outdoor',
parent_id: 2
}
Now I want my function to check for the parent category by using parent_id from database and return an array/object like this:
{
id: 2,
name: 'Furniture',
parent: {
id: 1,
name: 'Residential',
parent: {
id: ...,
name: ...,
parent: {
and so on..
}
}
}
}
This is what I have done so far:
* _get_category_parents(category, _array) {
if(_array === undefined) _array = []
if( category.parent_id !== 0 ) {
const c_parent = yield this.Database.from('categories').where('id', '=', category.parent_id)
_array.push({id: c_parent[0].id, name: c_parent[0].name})
yield this._get_category_parents(c_parent[0], _array)
}
return _array
}
And calling this function like this:
const parents = yield this._get_category_parents(category)
This returns me an array of parents like this:
[
{
"id": 2,
"name": "Furniture"
},
{
"id": 1,
"name": "Residential"
}
]
I want Residential object to be appended in Furniture's parent node.
I have spent too much time on this but not getting what I want. Any help would be deeply appreciated.
What you want to think about is a recursive solution.
Since you're calling a database, it's probably unlikely, but if the lookup by id is synchronous, you might do it with code something like the following (note that I'm faking a db here):
const getHierarchy = (lookup, child) => {
const {id, name, parent_id} = lookup(child) ||
{id: null, name: null, parent_id: 0}
return parent_id == 0
? {id, name, parent_id}
: {...{id, name}, ...{parent: getHierarchy(lookup, {parent_id})}}
}
const items = [
{id: 1, name: 'Residential', parent_id: 5},
{id: 2, name: 'Furniture', parent_id: 1},
{id: 3, name: 'Other', parent_id: 0},
{id: 4, name: 'FooBar', parent_id: 3},
{id: 5, name: 'Stuff', parent_id: 0}
]
const lookup = child => items.find(item => item.id == child.parent_id)
const item = {id: 65, name: 'Outdoor', parent_id: 2}
console.log(getHierarchy(lookup, item))
You would have to write an appropriate lookup function, presumably using this.Database.from(...). You might also want to simplified version that bakes in your lookup function, in which case, you might write
const getAncestry = (item) => getHierarchy(lookup, item)
If, as seems more likely, your lookup is asynchronous, then that will affect getHierarchy and how you call it. Here's one possibility:
const getHierarchy = async (lookup, child) => {
const {id, name, parent_id} = await lookup(child) ||
{id: null, name: null, parent_id: 0}
return parent_id == 0
? {id, name, parent_id}
: {...{id, name}, ...{parent: await getHierarchy(lookup, {parent_id})}}
}
const items = [
{id: 1, name: 'Residential', parent_id: 5},
{id: 2, name: 'Furniture', parent_id: 1},
{id: 3, name: 'Other', parent_id: 0},
{id: 4, name: 'FooBar', parent_id: 3},
{id: 5, name: 'Stuff', parent_id: 0}
]
const lookup = async child => new Promise(
(resolve, reject) => setTimeout(
() => resolve(items.find(item => item.id == child.parent_id)),
1000
)
)
const getAncestry = async item => getHierarchy(lookup, item)
const item = {id: 65, name: 'Outdoor', parent_id: 2}
getAncestry(item).then(console.log)
Note the change in how you call the function. You need to call .then() on the resulting promise to get any useful behavior.

Recursive function to find top level parent given an id

Given the following data set:
const accounts = [
{id: 2, children: [1,22,69], parentId: null},
{id: 3, children: [140, 122, 580], parentId: null},
{id: 1, children: [4,5,6], parentId: 2},
{id: 22, children: [8,9,2], parentId: 2},
{id: 4, children: [45,54,61], parentId: 1},
{id: 6, children: [40,89,20], parentId: 1},
{id: 40, children: [], parentId: 6},
....
]
I need to create a function that takes and id as argument and returns a tree, starting with the top most level parent and it's children (and siblings).
In the above example, there are only 2 top level "accounts", id:2 and id:3. So the function call might look like findTree(89) , it should return the tree starting with the account id 2, and it's children, but will obviously leave out account id 3 and it's children, since that top level account has nothing to do with top level account of id 2, so the ideal response would be:
{
id: 2,
children: [
{ id: 1, children: [{id: 540, children: [{ id: 78},{}], parentId:1], parentId: 2},
.....
],
parentId: null
}
What would be the best way to go about it ? I've tried a recursive function but I'm not getting anywhere near to a solution.
EDIT: Here part of the code:
(groupArray is an array containing all items in a flat list, without hierarchy)
const makeTreeById = itemId => {
const startNode = _.find(groupArray, {id: itemId}) // grab the actual item, not the id
findTopParent(startNode)
}
and then the findTopParent fn
const findTop = item => {
let top = item;
if(top.parentId) {
top = _.find(groupArray, {id: top.parentId}
return findTop(top)
}
return top;
}
I was creating that function to simply have it return the top level account and from there I was planning on constructing the actual tree, the problem is that top does get me the top level but at some point it get reassigned with the immediate parent.
SECOND EDIT: Sorry about all the confusion guys, as you can see, I'm really new.
I have an array that includes all items I would need. So it kinda looks like this:
// children here are only ids, not the actual elements, the element are part of // the list, but the children array for each element is just a reference.
data = [
{id: 1, children: [4,5,6], parentId: null},
{id: 2, children: [7,8,9], parentId: null},
{id: 3, children: [10,11,12], parentId: null},
{id: 4, children: [13,14,15], parentId: 1},
{id: 10, children: [20,21,22], parentId: 3}
{id: 14, children: [], parentId: 4}
....
]
You can find the desired results with function topParent. Just look for parent being null in each iteration.
const accounts = [
{id: 2, children: [1,22,69], parentId: null},
{id: 3, children: [140, 122, 580], parentId: null},
{id: 1, children: [4,5,6], parentId: 2},
{id: 22, children: [8,9,2], parentId: 2},
{id: 4, children: [45,54,61], parentId: 1},
{id: 6, children: [40,89,20], parentId: 1},
{id: 40, children: [], parentId: 6}
];
function topParent(id) {
var obj = accounts.find((v) => v.id == id);
return obj.parentId == null ? obj : topParent(obj.parentId)
}
console.log(topParent(6));
actually they are many way to achieve the expected tree. In performance manner you should determine if you will have complexity (in term of iteration) on the deep of your tree or | and on how many items in total you will have.
I have assume the complexity will be more on how many items in total you will have.
exemple : big amount of accounts with only small amount of nested childrens.
Introduction : Following you have type and sample array.
interface IEntity {
id: number;
children: number[];
parentId?: number;
}
interface IEntityNested {
id: number;
children: IEntityNested[];
parentId?: number;
}
const accounts: IEntity[] = [
{id: 1, children: [3], parentId: null},
{id: 2, children: [], parentId: null},
{id: 3, children: [4], parentId: 1},
{id: 4, children: [], parentId: 3},
];
For that i prupose you to start by searching for any particular id what is the top of you tree. The element which doesn't have any other top element.
const findTopParent = (id: number): IEntity => {
let account = accounts.find(acc => acc.id === id);
if(account.parentId !== null) {
account = findTopParent(account.parentId);
}
return account;
};
For id 4 it should return account id 1
const topParent = findTopParent(4);
console.log(topParent.id); // Print 1.
then from your topParent you can build the nested tree from the top to the bottom.
const buildTreeFromSpecificAccount = (account: IEntity): IEntityNested => {
const nestedAccount = {...account,children: []};
account.children.forEach(childId => {
nestedAccount.children.push(
buildTreeFromSpecificAccount(
accounts.find(acc => acc.id === childId)
)
);
})
return nestedAccount;
}
// Build the tree from the top parent.
const tree = buildTreeFromSpecificAccount(topParent);
And voilĂ  !
Side note :
You can way more improve the performance by changing your data array by indexed object like following :
const accountOrdered: {[id: number]: IEntity} = {
1: {id: 1, children: [3], parentId: null},
2: {id: 2, children: [], parentId: null},
3: {id: 3, children: [4], parentId: 1},
4: {id: 4, children: [], parentId: 3},
};
Like this instead of doing accounts.find(acc => acc.id === childId) looping on your array to find entry by id. you can do accountOrdered[childId]
live sample

nested array object comparison with another array of elements and create new array using Javascript or ES6

I have a complex array's like shown below
sectionDetail = [{id: 1, name:'ma'}, {id: 2, name:'na'}, {id: 3, name:'ra'}, {id: 4, name:'ka'}, {id: 5, name:'pa'}];
abc = [{id:'1', name:'zam', sections:['1',4]}, {id:'2', name:'dam', sections:['3']}, {id:'3', name:'nam', sections:['2','4']}];
Now I have to loop through the abc with respect to sections to replace the array elements with their respective sectionDetail values
I have tried by looping it to a new variable but my sections is getting replaced every time. below is the code i tried.
const matchingBoost = [];
const getCategoryBasedBoostList = [];
abc.forEach((item, i) => {
sectionDetail.forEach((val, index) => {
item.section.forEach((value, x) => {
if (value == val.Id) {
matchingBoost.push(val);
}
});
});
getCategoryBasedBoostList.push({
Name: item.Name,
Boost: matchingBoost
});
});
so basically I'm looking for a new array something like this
xyz = [{name:'zam', sections:[{id: 1, name:'ma'}, {id: 4, name:'ka'}]},
{name:'dam', sections:[{id: 3, name:'ra'}]}, {name:'nam', sections:[{id: 2, name:'na'}, {id: 4, name:'ka'}]}];
hoping I made sense and hoping for some response.
You can basically filter the sections from sectionDetail based on whether the object.id inside it is included in the sections of abc. I have mapped the indexes to number in both cases since one was string and the other was integer.
sectionDetail = [{id: 1, name:'ma'}, {id: 2, name:'na'}, {id: 3, name:'ra'}, {id: 4, name:'ka'}, {id: 5, name:'pa'}];
abc = [{id:'1', name:'zam', sections:['1',4]}, {id:'2', name:'dam', sections:['3']}, {id:'3', name:'nam', sections:['2','4']}];
xyz = abc.map(item => ({...item, sections: sectionDetail.filter(sect => item.sections.map(id => parseInt(id)).includes(parseInt(sect.id)))}));
console.log(xyz);
You could take a Map and then map the data with the items of sectionDetail.
var sectionDetail = [{ id: 1, name: 'ma' }, { id: 2, name: 'na' }, { id: 3, name: 'ra' }, { id: 4, name: 'ka' }, { id: 5, name: 'pa' }],
data = [{ id: '1', name: 'zam', sections: ['1', 4] }, { id: '2', name: 'dam', sections: ['3'] }, { id: '3', name: 'nam', sections: ['2', '4'] }],
map = new Map(sectionDetail.map(o => [o.id, o])),
result = data.map(({ name, sections }) =>
({ name, sections: sections.map(id => map.get(+id)) })
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
So you want to remove the id from the abc objects and replace the sections array elements with the corresponding details objects? This looks like a job for forEach and map! The code I'm about to show also does a little bit of pre-processing of the sections array to make the overall code more efficient.
const sections = sectionDetail.reduce((result, section) => {
result[section.id] = section;
return result;
}, {});
abc.forEach(item => {
delete item.id;
item.sections = item.sections.map(id => sections[id]);
});
Try like this:
const sectionDetail = [
{ id: 1, name: 'ma' },
{ id: 2, name: 'na' },
{ id: 3, name: 'ra' },
{ id: 4, name: 'ka' },
{ id: 5, name: 'pa' }];
const abc = [
{ id: '1', name: 'zam', sections: ['1', 4] },
{ id: '2', name: 'dam', sections: ['3'] },
{ id: '3', name: 'nam', sections: ['2', '4'] }
];
const desired = abc.map(({id, name, sections}) => {
return {id, name, sections : sectionDetail.filter(f => {
return sections.map(s => +s).includes(f.id)
})};
})
console.log(desired);
where +s is casting to Number type.

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. :)

Categories