Recursive function to create a list from another deep list - javascript

Another question about recursive function, I cant get my head arround them.
I have a list with groups that can have any depth, an example:
{
Id: 1,
Name:"Root",
Children: [
{
Id: 1,
Name:"",
Children: [
{
Id: 1,
Name:"",
Children: [
{
Id: 1,
Name:"",
Children: []
},
]
},
]
},
{
Id: 2,
Name:"",
Children: []
},
{
Id: 3,
Name:"",
Children: []
},
]
}
I show these groups in a dropdown that the user can select.
What I need to do is when the user clicks on any group, I need to show all users that are a part of that group AND its subgroups.
The information about which users belong to the group and its subgroups is hold by the userlist. That list is flat and every user has an prop that contains an membership array.
I have re-written this method below several times, this is the closest I get, but this more than doubles the expected lenght because I get dublicates.
const getAllUsersInGroup = (group, usersFiltered) => {
if (!group.Children.length) return usersFiltered.flat();
return group.Children.flatMap((g) => {
return getAllUsersInGroup(
g,
[...usersFiltered, users.filter((u) => u.Memberships.some((m) => m.GroupId === g.Id))]
);
});
};
Another test returns almost all but there is missing users on bigger groups with many subgroups.
const getAllUsersInGroup = (group, userss) => {
if (!group.Children.length) return [...userss].flat();
return group.Children.flatMap((g) => {
return getAllUsersInGroup(g,
users.filter((u) => u.Memberships.some((m) => m.GroupId === g.Id)),
);
});
};
I must be stuck in some wrong thinking or just pure stupid..
Maybe I dont need to check the Children lenght and just go thro them all, but as I understand it you need some statment that stops the method.
A little help would be much appreciated!
Regards

Related

Javascript filter parent array based on child containing value

I am trying to filter the parent array products based on the selected value which should be contained as the child. For example in my case I was trying to get all the product objects as an array which contained the string "iphone". It is not working for me and I can't seem to locate my error. Please may someone help.
What I have tried:
const selected = 'iphone'
const products = [
{
id: "4irnflpd0",
productItem: [ "iphone", "ipad", "kindle"],
},
{
id: "glrscb1m3s9k",
productItem: ["airpods","iphone",],
},
{
id: "uumkugk3jxof",
productItem: ["macbook","cable"]
},
]
var filtered = products.map(o=>{
o = Object.assign({},o); //Clone the object. So any changes will not affect the original array.
o.productItem.map(p=>{ //Use map to loop thru the products
p = p.filter(v=>v === selected); //Filter the products which include selected
});
return o;
})
Expected array output:
const products = [
{
id: "4irnflpd0",
productItem: [ "iphone", "ipad", "kindle"],
},
{
id: "glrscb1m3s9k",
productItem: ["airpods","iphone",],
},
]
Just a simple filter() method should work for us in this case. T further simplify, we can also use object restructuring, so instead of writing product.productItem.includes(selected), we'll only need to write productItem.includes(selected).
All we have to do is filter the source array by which items include the selected value:
const selected = 'iphone';
const products = [
{ id: "4irnflpd0", productItem: ["iphone", "ipad", "kindle"] },
{ id: "glrscb1m3s9k", productItem: ["airpods", "iphone", ] },
{ id: "uumkugk3jxof", productItem: ["macbook", "cable"] },
];
const filtered = products.filter(({productItem}) => productItem.includes(selected));
console.log(filtered);
If you'd prefer not to use Object destructuring, just swap that filter line for this more traditional one:
products.filter(p => p.productItem.includes(selected))
If you're not sure that every single item in your array will have the productItem key, then you should use optional chaining to prevent an error being thrown. This is as simple as adding ? before after property name. Here it is all put together:
products.filter(p => p.productItem?.includes(selected))
First of all no need to make a copy since with map function you allocated the result to new variable without changing the new array
So you need to do this
const filtered = products.map(p=>{
return p.productItem.includes(selected)
})
You can use the filter() methods on products array and find if the selected item is available in the productItems
const selected = 'iphone'
const products = [
{ id: "4irnflpd0", productItem: [ "iphone", "ipad", "kindle"] },
{ id: "glrscb1m3s9k", productItem: ["airpods","iPhone",] },
{id: "uumkugk3jxof", productItem: ["macbook","cable"] }
]
var filtered = products.filter(product =>
product.productItem?.includes(selected));
console.log(filtered);

How can I group specific items within an object in the same array and delete them from the core array?

I am trying to achieve something a bit complex.
I am working on a grouping feature where I have to get the items containing the key checked: true then I need to put them together within the key/array named questionGroup in the first item in the array with checked: true and then deleting the other items that were grouped.
See the structure of the data:
const dummyQuestions = [
{
id: 1,
checked: false,
question: {...},
questionGroup: []
},
{
id: 2,
checked: true,
question: {...},
questionGroup: []
},
{
id: 3,
checked: false,
question: {...},
questionGroup: []
},
{
id: 4,
checked: true,
question: {...},
questionGroup: []
},
{
id: 5,
checked: true,
question: {...},
questionGroup: []
}
];
So lets say you clicked on a certain button which fires a grouping function, as you noticed index 1, 3 and 4 have checked: true, so this must happen:
const dummyQuestions = [
{
id: 1,
checked: false,
question: {...},
questionGroup: []
},
{
id: 2,
checked: true,
question: {...},
questionGroup: [
{
id: 2,
checked: true,
question: {...},
questionGroup: []
},
{
id: 4,
checked: true,
question: {...},
questionGroup: []
},
{
id: 5,
checked: true,
question: {...},
questionGroup: []
}
]
},
{
id: 3,
checked: false,
question: {...},
questionGroup: []
}
];
That's how the data must be after grouping it.
Do you get the idea?
I created a reproducible demo => https://codesandbox.io/s/ts-react-grouping-odxs1?file=/src/App.tsx
EDIT
This is the relevant code:
const handleGroupQuestions = (): void => {
const questionAddedCopy: VariableConfig[] = [...questionAdded];
// Only the questions with checked === true will be grouped.
const filteredCopy: VariableConfig[] = questionAddedCopy.filter(
(q: VariableConfig) => q.checked === true
);
const grouped: VariableConfig[] = [
...questionAdded.filter((q: VariableConfig) => filteredCopy.includes(q))
];
for (let i = 0; i < grouped.length; i++) {
// Here I compare the ids and I put them together into the key questionGroup
if (filteredCopy[i].id === grouped[i].id) {
// I had to set any instead of VariableConfig type because otherwise TS throws an error
addQuestion((q: any) => {
const questionsCopy = [...q];
const mergedQuestions: VariableConfig[] = [
...questionsCopy,
{ ...grouped[i], questionGroup: grouped }
];
// With this `unique` function what I am trying to achieve (which I am not) is
// to have a unique ID per every question/item in the array.
// I need to add the questions to questionGroup to the highest item containing check = true
// I need to delete from the main array, the items going inside questionGroup.
const unique = [
...mergedQuestions.filter((c) => c.id !== questionsCopy[i].id)
];
return unique;
});
break;
}
}
};
And to explain more in deep and to answer your questions.
We see that the first item in the array with checked: true is the item with the id 2, so that will be the place where I have to stored the other items with checked: true because that is the highest item in the array, then I need to delete from the array the items that were grouped into questionGroup.
The id 2 in this case is repeated and that is a requirement, because one id is for the parent and the other one within questionGroup is its own child, get it? Like if it is storing itself into questionGroup.
This is just a grouping functionality where I am saving some steps. Imagine every item contains a checkbox, so the items that you want to group, you have to check them first, then click the GROUP THEM button, the checked items disappear and then get together into the highest checked index, keeping its id and storing itself as well.
Take a look at the arrays I posted. There you can see what is the exact output I need.
Here is one technique:
// helper function
const addChecked = (q, qs) =>
({...q, questionGroup: qs .filter (p => p .checked)})
// main function
const nestChecked = (qs, found = false) =>
qs .flatMap (q => q .checked ? found ? [] : (found = true, [addChecked (q, qs)]) : [q])
// sample data
const dummyQuestions = [{id: 1, checked: false, question: 'foo', questionGroup: []}, {id: 2, checked: true, question: 'bar', questionGroup: []}, {id: 3, checked: false, question: 'baz', questionGroup: []}, {id: 4, checked: true, question: 'qux', questionGroup: []}, {id: 5, checked: true, question: 'corge', questionGroup: []}]
// demo
console .log (JSON .stringify (nestChecked (dummyQuestions), null, 4))
.as-console-wrapper {max-height: 100% !important; top: 0}
We have a simple helper function that groups all the checked elements of the input into (a copy of) a specific question.
Our main function iterates over the questions with .flatMap, which allows us to do a filter and map in a single traversal. We maintain a flag, found which tells us if we've already handled the checked elements. For each question, if it's not checked, we simply include it, by returning it wrapped in an array, as [q] from the flatMap callback. If it is checked, we evaluate the found flag. If that is set to true, then we return and empty array. If it's not, we return the result of calling our helper function above, again wrapped in an array. (These array wrappers are not specifically needed for this problem; but I like to return a consistent type in the flatMap callback.)
We could easily inline that helper function, as it's only called once. In fact that's how I originally wrote it. But it feels cleaner to me separated.
It's not clear to me if there is some iterative process which will then further nest these. If so, you might have to do something more sophisticated in the helper function dealing wit already-populated questionGroup fields. But that would probably be for a different question.
Finally, we also might want to avoid that default parameter. There are sometimes good reasons to want to do so. I leave this change as an exercise for the reader. :-)

How to find the id of an object within an array having only one identified attribute of the "child" array

I would like help to develop a function using the most optimized approach in Javascript to find the id of the "parent" object having only a code of an "child" object (inside dataArray).
Example:
getIdParent("240#code") -> return "1"
[
{
id: 0,
dataArray:[
{
id: 182,
code: "182#code",
name: "Product1"
},
{
id: 183,
code: "183#code",
name: "Product2"
}
]
},
{
id: 1,
dataArray:[
{
id: 240,
code: "240#code",
name: "Product2"
},
{
id: 341,
code: "341#code",
name: "Product2"
}
]
}
]
Thanks in advance.
You don't really have a lot of options here.
The only real optimisation I can think of is based on how often you expect to call this function.
If it's only once, you just need to iterate the array, searching for values and return as early as possible to prevent unnecessary iterations.
function getIdParent(childCode) {
return arr.find(parent =>
parent.dataArray.some(({ code }) => code === childCode))?.id
}
If multiple times, you should build up an indexed map of child code to parent object and then reference that
const arr = [{"id":0,"dataArray":[{"id":182,"code":"182#code","name":"Product1"},{"id":183,"code":"183#code","name":"Product2"}]},{"id":1,"dataArray":[{"id":240,"code":"240#code","name":"Product2"},{"id":341,"code":"341#code","name":"Product2"}]}]
const codeMap = arr.reduceRight((map, parent) => {
parent.dataArray.forEach(({ code }) => {
map.set(code, parent)
})
return map
}, new Map())
function getIdParent(code) {
return codeMap.get(code)?.id
}
let search = ["240#code", "182#code", "NotFound"]
search.forEach(code => {
console.log("Parent ID for", code, "=", getIdParent(code))
})

Recursively filter an array of objects according to an array of conditions

I have an array with objects that I want to filter according to an indefinite number of conditions passed as parameters.
Here's how I filter the array a with a array of conditions in hide ()
const statuses = [
{
id: 0,
name: 'archived'
},
{
id: 1,
name: 'coming',
hide: (...filterParam) => filterParam.every(rule => rule)
}
];
const filteredStatuses = statuses.filter(element => {
switch (element.id) {
case 1:
return !element.hide(this.isTopTabs());
// other cases with others logic
default:
return true;
}
});
Now if each object can have its own children object array like that:
const statuses = [
{
id: 'statuses',
name: 'treeName',
children: [
{
id: 1,
name: 'inProgress',
hide: (...filterParam) => filterParam.every(Boolean)
},
{
id: 2,
name: 'coming',
checked: false,
hide: (...filterParam) => filterParam.every(Boolean)
}
]
}
];
How I can recursively iterate the same way?
Do you have a better way to filter this array dynamically by avoiding the switch / case?
And finally how to type the rest parameters with a generic like that hide: <T>(...filterParam: Array<T>) => filterParam.every(Boolean) ?
How I can recursively iterate the same way?
const filterRecursively = elements => filterStatuses(elements).map(it => ({ ...it, children: it.children && filterRecursively(it.children) }));
Do you have a better way to filter this array dynamically by avoiding the switch / case?
Why? Whats wrong with the switch case? Isn't it fancy enough?
And finally how to type the rest parameters with a generic like that hide: (...filterParam: Array) => filterParam.every(Boolean) ?
<T>(...filterParam: Array<T>) => boolean

How to build tree array from flat array of object with category and subCategrie properties

I am trying to build tree array from flat array, each item in the flat array has two property need to be used to build the tree array, they are 1. category. 2. subCategrie which is array of string.
let data = [
{
id: 1,
name: "Zend",
category: "php",
subCategory: ["framework"]
},
{
id: 2,
name: "Laravel",
category: "php",
subCategory: ["framework"]
},
{
id: 3,
name: "Vesion 5",
category: "php",
subCategory: ["versions"]
},
{
id: 4,
name: "Angular",
category: "frontend",
subCategory: ["framework", "typescript"]
},
{
id: 5,
name: "Aurelia",
category: "frontend",
subCategory: ["framework", "typescript"]
},
{
id: 6,
name: "JQuery",
category: "frontend",
subCategory: []
}
];
It should be
let tree = [
{
name: "php",
children: [
{
name: "framework",
children: [
{
id: 1,
name: "Zend"
},
{
id: 2,
name: "Laravel"
}
]
},
{
name: "versions",
children: [
{
id: 3,
name: "Vesion 5"
}
]
}
]
}
// ...
];
Is there any article, link solving similar problem?
I gave it many tries but stuck when trying to build the sub categories children.
Here's my last attempt which throws error and I know it's wrong but it's for the ones who want to see my attempts
const list = require('./filter.json')
let tree = {};
for (let filter of list) {
if (tree[filter.category]) {
tree[filter.category].push(filter);
} else {
tree[filter.category] = [filter];
}
}
function buildChildren(list, subcategories, category, index) {
let tree = {}
for (let filter of list) {
if (filter.subcategory.length) {
for (let i = 0; i < filter.subcategory.length; i++) {
let branch = list.filter(item => item.subcategory[i] === filter.subcategory[i]);
branch.forEach(item =>{
if (tree[filter.subcategory[i]]){
tree[filter.subcategory[i]] = tree[filter.subcategory[i]].push(item)
}else{
tree[item.subcategory[i]] = [item]
}
})
}
}
}
console.log('tree ', tree);
}
Heads up, For javascript I usually use Lodash (usually written as _ in code) but most of these methods should also be built in to the objects in javascript (i.e. _.forEach = Array.forEach())
const tree = [];
// First Group all elements of the same category (PHP, Frontend, etc.)
data = _.groupBy(data, 'category');
_.forEach(data, function (categoryElements, categoryName) {
// Each Category will have it's own subCategories that we will want to handle
let categorySubCategories = {};
// The categoryElements will be an array of all the objects in a given category (php / frontend / etc..)
categoryElements.map(function (element) {
// For each of these categoryies, we will want to grab the subcategories they belong to
element.subCategory.map(function (subCategoryName) {
// Check if teh category (PHP) already has already started a group of this subcategory,
// else initialize it as an empty list
if (!categorySubCategories[subCategoryName]) { categorySubCategories[subCategoryName] = []; }
// Push this element into the subcategory list
categorySubCategories[subCategoryName].push({id: element.id, name: element.name});
});
});
// Create a category map, which will be a list in the format {name, children}, created from
// our categorySubCategories object, which is in the format {name: children}
let categoryMap = [];
_.forEach(categorySubCategories, function (subCategoryElements, subCategoryName) {
categoryMap.push({name: subCategoryName, children: subCategoryElements});
});
// Now that we've grouped the sub categories, just give the tree it's category name and children
tree.push({name: categoryName, children: categoryMap});
});
};
The key to success here is to create an interim format that allows for easy lookups. Because you work with children arrays, you end up having to use filter and find whenever you add something new, to prevent duplicates and ensure grouping.
By working with a format based on objects and keys, it's much easier to do the grouping.
We can create the groups in a single nested loop, which means we only touch each item once for the main logic. The group has this format:
{ "categoryName": { "subCategoryName": [ { id, name } ] } }
Then, getting to the required { name, children } format is a matter of one more loop over the entries of this tree. In this loop we move from { "categoryName": catData } to { name: "categoryName", children: catData }
Here's an example that shows the two steps separately:
const data=[{id:1,name:"Zend",category:"php",subCategory:["framework"]},{id:2,name:"Laravel",category:"php",subCategory:["framework"]},{id:3,name:"Vesion 5",category:"php",subCategory:["versions"]},{id:4,name:"Angular",category:"frontend",subCategory:["framework","typescript"]},{id:5,name:"Aurelia",category:"frontend",subCategory:["framework","typescript"]},{id:6,name:"JQuery",category:"frontend",subCategory:[]}];
// { category: { subCategory: [ items ] } }
const categoryOverview = data.reduce(
(acc, { id, name, category, subCategory }) => {
// Create a top level group if there isn't one yet
if (!acc[category]) acc[category] = {};
subCategory.forEach(sc => {
// Create an array for this subCat if there isn't one yet
acc[category][sc] = (acc[category][sc] || [])
// and add the current item to it
.concat({ id, name });
});
return acc;
},
{}
)
const nameChildrenMap = Object
.entries(categoryOverview)
// Create top level { name, children } objects
.map(([cat, subCats]) => ({
name: cat,
children: Object
.entries(subCats)
// Create sub level { name, children } objects
.map(([subCat, items]) => ({
name: subCat,
children: items
}))
}))
console.log(nameChildrenMap);

Categories

Resources