Related
var array = [
{id: 1, name: "Father", parent_id: null},
{id: 2, name: "Child", parent_id: 1},
{id: 3, name: "Child", parent_id: 1},
{id: 4, name: "ChildChild", parent_id: 2},
{id: 5, name: "ChildChildChild", parent_id: 4}
]
for(var i in array){
if(array[i].parent_id == null){
console.log(array[i].name);
} else {
for(var j in array){
if(array[i].parent_id == array[j].id && array[j].parent_id == null){
console.log(">" + array[i].name);
for(var x in array){
if(array[i].id == array[x].parent_id){
console.log(">>" + array[x].name);
}
}
}
}
}
}
Output:
Father
>Child
>>ChildChild
>Child
I have this array which has id, name and parent_id. Right now it is fixed but it could have multiple arrays and can be nested for n amount of times.
What I am doing here is iterating through each array and trying to find which are the parents and which one is the child.
I want to know if there is a more efficient way to write this code. For instance, I added a fifth id but that would require another for loop and so on. The output would be the same just a printed out tree.
You can use a Map to key your nodes by id, and then use recursion to traverse them in depth first order:
var array = [{id: 1, name: "Father", parent_id: null},{id: 2, name: "Child", parent_id: 1},{id: 3, name: "Child", parent_id: 1},{id: 4, name: "ChildChild", parent_id: 2},{id: 5, name: "ChildChildChild", parent_id: 4}];
let map = new Map(array.map(({id}) => [id, []])).set(null, []);
array.forEach(node => map.get(node.parent_id).push(node));
function dfs(nodes, indent="") {
for (let node of nodes) {
console.log(indent + node.name);
dfs(map.get(node.id), indent+">");
}
}
dfs(map.get(null));
You could create a tree and then make the output.
const
print = ({ name, children = [] }) => {
console.log(name)
children.forEach(print);
},
array = [{ id: 1, name: "Father", parent_id: null }, { id: 2, name: "Child", parent_id: 1 }, { id: 3, name: "Child", parent_id: 1 }, { id: 4, name: "ChildChild", parent_id: 2 }, { id: 5, name: "ChildChildChild", parent_id: 4 }],
tree = function (data, root) {
var t = {};
data.forEach(o => {
Object.assign(t[o.id] = t[o.id] || {}, o);
t[o.parent_id] = t[o.parent_id] || {};
t[o.parent_id].children = t[o.parent_id].children || [];
t[o.parent_id].children.push(t[o.id]);
});
return t[root].children;
}(array, null);
tree.forEach(print);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
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
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.
i neeed to merge two arrays: Categories and Products. Each product has a category object. I need to organize by category, include the category object and keep the empty categories. GroupBy function include only one parameter.
const Categories= [
{id: 1, 'name': 'category1'}
{id: 2, 'name': 'category2'},
{id: 3, 'name': 'category3'},
{id: 4, 'name': 'category4'},
]
const Products= [
{id: 1, 'name': 'product1', category: {id: 1, name: 'category1'}},
{id: 2, 'name': 'product2', category: {id: 1, name: 'category1'}},
{id: 3, 'name': 'product3', category: {id: 2, name: 'category2'}},
{id: 4, 'name': 'product4', category: {id: 2, name: 'category2'}},
]
expected result
const result = [
{
category: {id: 1, name: 'category1'},
products:[{id:1, name: 'produt1'}, {id: 2, name: 'produto1'} ]
},
{
category: {id: 2, name: 'category2'},
products:[{id:3, name: 'produt3'}, {id: 4, name: 'produto4'} ]
},
{
category: {id: 3, name: 'category3'},
products:[]
},
{
category: {id: 4, name: 'category4'},
products:[]
},
]
attempts:
for (i = 0; i < categoriesJson.length; i++) {
categoriesJson[i] = _.assign({}, categoriesJson[i], { products: [] })
for (j = 0; j < productsJson.length; j++) {
if(productsJson[j].categoryId.objectId === categoriesJson[i].objectId){
categoriesJson[i].products.push(productsJson[j])
}
}
}
Concat the Categories (formatted by to a Product format) to the Products, group by the category.id, and then map each group - category is taken from the 1st item, while products are the the items in groups, without the category, and empty items are rejected:
const Products = [{"id":1,"name":"product1","category":{"id":1,"name":"category1"}},{"id":2,"name":"product2","category":{"id":1,"name":"category1"}},{"id":3,"name":"product3","category":{"id":2,"name":"category2"}},{"id":4,"name":"product4","category":{"id":2,"name":"category2"}}]
const Categories = [{"id":1,"name":"category1"},{"id":2,"name":"category2"},{"id":3,"name":"category3"},{"id":4,"name":"category4"}]
const result = _(Products)
.concat(Categories.map(category => ({ category })))
.groupBy('category.id')
.map(group => ({
category: _.head(group).category,
products: _(group)
.map(o => _.omit(o, 'category'))
.reject(_.isEmpty)
.value()
}))
.value()
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>
And the same idea with lodash/fp. Wrap the _.flow() with the _.useWith() function, and preformat the Categories (2nd param) to fit the Categories. The rest is similar to the lodash chain.
const { useWith, identity, flow, concat, groupBy, map, head, omit, reject, isEmpty } = _
const formatProducts = flow(map(omit('category')), reject(isEmpty))
const fn = useWith(flow(
concat,
groupBy('category.id'),
map(group => ({
category: head(group).category,
products: formatProducts(group)
}))
), [identity, map(category => ({ category }))])
const Products = [{"id":1,"name":"product1","category":{"id":1,"name":"category1"}},{"id":2,"name":"product2","category":{"id":1,"name":"category1"}},{"id":3,"name":"product3","category":{"id":2,"name":"category2"}},{"id":4,"name":"product4","category":{"id":2,"name":"category2"}}]
const Categories = [{"id":1,"name":"category1"},{"id":2,"name":"category2"},{"id":3,"name":"category3"},{"id":4,"name":"category4"}]
const result = fn(Products, Categories)
console.log(result)
<script src='https://cdn.jsdelivr.net/g/lodash#4(lodash.min.js+lodash.fp.min.js)'></script>
If lodash is not a requirement in the solution, this is how I did it with plain javascript;
const Categories= [
{id: 1, 'name': 'category1'},
{id: 2, 'name': 'category2'},
{id: 3, 'name': 'category3'},
{id: 4, 'name': 'category4'}
];
const Products= [
{id: 1, 'name': 'product1', category: {id: 1, name: 'category1'}},
{id: 2, 'name': 'product2', category: {id: 1, name: 'category1'}},
{id: 3, 'name': 'product3', category: {id: 2, name: 'category2'}},
{id: 4, 'name': 'product4', category: {id: 2, name: 'category2'}},
];
const result = [];
for (let index in Categories) {
let category_id = Categories[index].id;
result.push({
category: Categories[index],
products: GetProductsWithCategoryId(category_id)
});
}
function GetProductsWithCategoryId(category_id) {
let products = [];
for (let index in Products) {
if (Products[index].category.id == category_id) {
products.push({
id: Products[index].id,
name: Products[index].name
});
}
}
return products;
}
console.log("result:", result);
Using reduce, create a mappedProducts object which groups the Products based on the category.id. Like this:
{
"1": [{ id: 1, name: "product1" }, { id: 2, name: "product2" }],
"2": [{ id: 3, name: "product3" }, { id: 4, name: "product4" }]
}
Then, map the Categories array and get the output for each category
const Categories=[{id:1,name:"category1"},{id:2,name:"category2"},{id:3,name:"category3"},{id:4,name:"category4"},],
Products=[{id:1,name:"product1",category:{id:1,name:"category1"}},{id:2,name:"product2",category:{id:1,name:"category1"}},{id:3,name:"product3",category:{id:2,name:"category2"}},{id:4,name:"product4",category:{id:2,name:"category2"}}];
const mappedProducts = Products.reduce((acc, { category, ...rest }) => {
acc[category.id] = acc[category.id] || [];
acc[category.id].push(rest)
return acc;
}, {})
const output = Categories.map(category => ({
category,
products: mappedProducts[category.id] || []
}))
console.log(output)
In a single function. Lodash is not necessary:
const Categories = [
{ id: 1, name: "category1" },
{ id: 2, name: "category2" },
{ id: 3, name: "category3" },
{ id: 4, name: "category4" }
];
const Products = [
{ id: 1, name: "product1", category: { id: 1, name: "category1" } },
{ id: 2, name: "product2", category: { id: 1, name: "category1" } },
{ id: 3, name: "product3", category: { id: 2, name: "category2" } },
{ id: 4, name: "product4", category: { id: 2, name: "category2" } }
];
function combine(categories, products) {
return categories.reduce((list, category) => {
const nextItem = {
category,
products: [
products.filter(p => p.category.id === category.id).map(
({ id, name }) => ({
id,
name
})
)
]
};
list.push(nextItem);
return list;
}, []);
}
const result = combine(Categories, Products)
Now for your information, if you had a huge list of categories and/or products, this wouldn't be the ideal solution as there is a lot of looping involved. Instead, you would cache products in such a way that you only ever need to look at a given product once (rather than looking at every product for every category). With a small data set, this optimization isn't necessary.
In ES6 using find or filter I'm quite comfortable iterating through to find an element in an array using a value.
However, I'm trying to get a value from a parent array based upon a value from a nested array.
For example, in this data structure:
products: [
{
id: 01,
items: [
{
id: 01,
name: 'apple'
},
{
id: 02,
name: 'banana'
},
{
id: 03,
name: 'orange'
}
]
},
{
id: 02,
items: [
{
id: 01,
name: 'carrot'
},
{
id: 02,
name: 'lettuce'
},
{
id: 03,
name: 'peas'
}
]
},
{
id: 03,
items: [
{
id: 01,
name: 'eggs'
},
{
id: 02,
name: 'bread'
},
{
id: 03,
name: 'milk'
}
]
}
]
If I know the name or id of the object milk, is there a way to find out the id of the element it's nested within?
Currently I have this:
products.find((product) => {
product.find((prod) => {
return prod.name === 'milk';
});
});
Which only returns the object containing milk.
You have to return something from the callback of the outer find. In fact, for the inner iteration you shouldn't use find but rather some that returns a boolean for whether an element matching the condition exists within the arrray:
products.find((product) => {
return product.items.some((item) => {
//^^^^^^
return item.name === 'milk';
});
});
or in short:
products.find(product => product.items.some(item => item.name === 'milk'));
Then check whether find found something (not null!) and get its .id, the result should be 03. Alternatively, you can filter for the products containing milk as an item and then map all the results to their id:
products.filter(product =>
product.items.some(item => item.name === 'milk');
).map(product =>
product.id
) // [03]
I know you mention ES6, but in this case (and if you want to return the inner object) I believe it's better using for/of instead of map/reduce/find:
for (let p of products) {
for (let i of p.items) {
if (i.name === 'milk') return i;
}
}
Another approach:
products
.map((category) => category.items)
.flat()
.find((product) => product.name === 'milk');
UPDATE
As Ricardo Marimon commented, reduce does not break so it keeps searching over the array, So with that in mind as I don't like to use for loops imperative way of programming, its possible to break early from a reduce by mutating the used array, but that would also be bad, so instead its possible to make a copy and mutate the copy instead too.
// slice creates a copy of products
return products.slice(0).reduce((prev, product, i, arr) => {
console.log(i);
const findItem = prev || product.items.find(item => item.name === 'milk');
if (typeof findItem !== 'undefined') arr.splice(1); // ejects early
return findItem;
}, undefined);
const products = [
{id: 1, items: [
{id: 1, name: 'apple'},
{id: 2, name: 'banana'},
{id: 3, name: 'orange'}
]},
{id: 2, items: [
{id: 1, name: 'carrot'},
{id: 2, name: 'lettuce'},
{id: 3, name: 'milk'}
]},
{id: 3, items: [
{id: 1, name: 'eggs'},
{id: 2, name: 'bread'},
{id: 3, name: 'peas'}
]}
];
const findItem = products.slice(0).reduce((prev, product, i, arr) => {
console.log(i);
const findItem = prev || product.items.find(item => item.name === 'milk');
if (typeof findItem !== 'undefined') arr.splice(1); // ejects early
return findItem;
}, undefined);
console.log(findItem);
OLD
The accepted answer didn't do it for me because I wanted the result of the inner find, using both it always gave me the result of the outer filter/find, and I had to use the resulting array to find the value again.
So instead I used reduce with short-circuit to get the inner result.
// undefined as the initial value is necessary, otherwise it gets the first value of the array instead.
return products.reduce((prev, product) => prev || product.items.find(item => item.name === 'milk'), undefined);
const products = [
{id: 1, items: [
{id: 1, name: 'apple'},
{id: 2, name: 'banana'},
{id: 3, name: 'orange'}
]},
{id: 2, items: [
{id: 1, name: 'carrot'},
{id: 2, name: 'lettuce'},
{id: 3, name: 'peas'}
]},
{id: 3, items: [
{id: 1, name: 'eggs'},
{id: 2, name: 'bread'},
{id: 3, name: 'milk'}
]}
];
console.log(products.reduce((prev, product) => prev || product.items.find(item => item.name === 'milk'), undefined));
To get the item directly without doubling back to get the id/object:
const products = [
{ id: 01,
items: [ { id: 01, name: 'apple' },
{ id: 02, name: 'banana'},
{ id: 03, name: 'orange'}]},
{ id: 02,
items: [ { id: 01, name: 'carrot' },
{ id: 02, name: 'lettuce'},
{ id: 03, name: 'peas' }]},
{ id: 03,
items: [ { id: 01, name: 'eggs' },
{ id: 02, name: 'bread' },
{ id: 03, name: 'milk' }]}
]
let found;
for ( const category of products ){
found = category.items.find( item => item.name == "milk" )
if ( found ) break
}
console.log( found )
// == { id: 3, name: 'milk' }