I ran into a problem that I don’t know how to correctly compose a recursive function. the logic is such that if you delete an item in the application, then all items whose parentId is equal to the id of the item that decided to delete are deleted, and so on. I hope I explained well, I will be very grateful for the help!
const arr = [
{title: 'one', id: 1, parentId: null},
{title: 'two', id: 2, parentId: 1},
{title: 'three', id: 3, parentId: null},
{title: 'four', id: 4, parentId: 2},
{title: 'five', id: 5, parentId: 2},
{title: 'six', id: 6, parentId: 5},
{title: 'seven', id: 7, parentId: 3},
]
// if we decide to remove "one", the result will be
[
{title: 'three', id: 3, parentId: null},
{title: 'seven', id: 7, parentId: 3},
]
I'm stuck on a version that removes subtasks with only one parentId level
const deleteFunc = (array, value) => {
array = array.filter(item => item.id !== value);
const findSubTask = array.filter((item) => item.parentId === value);
array = array.filter(item => item.id !== findSubTask[0].id);
const findSecondSubTask = array.find(
(key) => key.parentId === findSubTask[0].id,
);
if (findSecondSubTask) {
return deleteFunc(array, findSubTask[0].id);
}
return array;
};
console.log(deleteFunc(arr, 1));
Get the IDs of all the descendants of the starting element using a recursive function. Then remove them.
const arr = [
{title: 'one', id: 1, parentId: null},
{title: 'two', id: 2, parentId: 1},
{title: 'three', id: 3, parentId: null},
{title: 'four', id: 4, parentId: 2},
{title: 'five', id: 5, parentId: 2},
{title: 'six', id: 6, parentId: 5},
{title: 'seven', id: 7, parentId: 3},
];
function getDescendantIds(id, arr) {
let result = [id];
arr.forEach(el => {
if (el.parentId == id) {
result = result.concat(getDescendantIds(el.id, arr));
}
});
return result;
}
function removeTitleAndDescendants(title, arr) {
let start = arr.find(el => el.title == title);
if (start) {
let allIds = new Set(getDescendantIds(start.id, arr));
return arr.filter(el => !allIds.has(el.id));
}
return [...arr];
}
let result = removeTitleAndDescendants("one", arr);
console.log(result);
console.config({
maxEntries: Infinity
});
There are definitely a number of approaches to this question.
You could go ahead and keep a dictionary that will map parent IDs -> Items, so that you could easily perform what you're triyng to do here in a an efficient manner.
Despite that, if we are looking specifically for a javascript solution, I'd try something along the lines of:
const arr = [
{title: 'one', id: 1, parentId: null},
{title: 'two', id: 2, parentId: 1},
{title: 'three', id: 3, parentId: null},
{title: 'four', id: 4, parentId: 2},
{title: 'five', id: 5, parentId: 2},
{title: 'six', id: 6, parentId: 5},
{title: 'seven', id: 7, parentId: 3},
]
function deleteFromList(titleToDelete) {
deletedList = [];
itemToDeleteIdx = arr.indexOf(i=>i.title === titleToDelete);
if(itemToDeleteIdx !== -1){
deletedList.push(arr[itemToDeleteIdx]);
arr.splice(itemToDeleteIdx, 1);
return deleteRecursive(deleteRecursive[0].parentId, deletedList);
} else {
return [];
}
}
function deleteRecursive(parentIdToDelete, deletedItemsList){
itemToDeleteIdx = arr.indexOf(i=>i.parentId === parentIdToDelete);
if(itemToDeleteIdx !== -1){
deletedItemsList.push(arr[itemToDeleteIdx]);
arr.splice(itemToDeleteIdx, 1);
return deleteRecursive(parentIdToDelete, deletedItemsList)
}
else {
return deletedItemsList;
}
}
console.log(deleteFromList('three'))
Obviously it's missing a lot of validations and corner cases, but that's the main flow of things.
The function you see below takes an array of item ids you want to delete so at the beginning you give an array with the id of the item you want to delete.
then it does two simple tasks: delete items in the passed array, find the ids of all items which has the previous item as their parentId and add them to an array you pass to the next call and so on.
function deleteItems(arrIds) {
if(arrIds.length === 0) return;
arr = arr.filter((item) => !arrIds.includes(item.id));
const newIds = [];
arr.forEach((item) => {
if(arrIds.includes(item.parentId)) newIds.push(item.id);
})
deleteItems(newIds);
};
deleteItems([1]);
Let's convert to a tree:
const arr = [
{title: 'one', id: 1, parentId: null},
{title: 'two', id: 2, parentId: 1},
{title: 'three', id: 3, parentId: null},
{title: 'four', id: 4, parentId: 2},
{title: 'five', id: 5, parentId: 2},
{title: 'six', id: 6, parentId: 5},
{title: 'seven', id: 7, parentId: 3},
];
var grouped = arr.reduce(function(agg, item) {
agg[item.id] = item
return agg;
}, {})
arr.forEach(function(item) {
if (item.parentId) {
grouped[item.parentId].children ??= []
grouped[item.parentId].children.push(item.id)
}
})
// this is our tree
console.log(grouped)
// now we can:
function get_children_deep(tree, id) {
var result = [];
function iterate(tree, id) {
var obj = tree[id];
(obj.children || []).forEach(function(child_id) {
result.push(child_id);
iterate(tree, child_id);
})
}
iterate(tree, id)
return result;
}
console.log("all descendants of 1: " + get_children_deep(grouped, 1))
.as-console-wrapper {max-height: 100% !important}
So based on that, here's the solution of deleting item and children by title. recursively.
const arr = [
{title: 'one', id: 1, parentId: null},
{title: 'two', id: 2, parentId: 1},
{title: 'three', id: 3, parentId: null},
{title: 'four', id: 4, parentId: 2},
{title: 'five', id: 5, parentId: 2},
{title: 'six', id: 6, parentId: 5},
{title: 'seven', id: 7, parentId: 3},
];
console.log(delete_by_title(arr, "one"));
function delete_by_title(arr, title) {
var grouped = arr.reduce(function(agg, item) {
agg[item.id] = item
return agg;
}, {})
arr.forEach(function(item) {
if (item.parentId) {
grouped[item.parentId].children ??= []
grouped[item.parentId].children.push(item.id)
}
})
function get_children_deep(tree, id) {
var result = [];
function iterate(tree, id) {
var obj = tree[id];
(obj.children || []).forEach(function(child_id) {
result.push(child_id);
iterate(tree, child_id);
})
}
iterate(tree, id)
return result;
}
function id_by_title(arr, title) {
return arr.find(function(item) {
return item.title == title
}).id;
}
var id = id_by_title(arr, title)
var to_delete = get_children_deep(grouped, id)
to_delete.push(id)
to_delete.forEach(function(id) {
delete grouped[id]
})
return Object.values(grouped);
}
if you want to return them then the below should do the trick
const arr = [
{title: 'one', id: 1, parentId: null},
{title: 'two', id: 2, parentId: 1},
{title: 'three', id: 3, parentId: null},
{title: 'four', id: 4, parentId: 2},
{title: 'five', id: 5, parentId: 2},
{title: 'six', id: 6, parentId: 5},
{title: 'seven', id: 7, parentId: 3},
]
const deleteId = (val) => {
const resultArray = [];
const index = arr.findIndex((n) => n.id === val);
for(let i =0; i < arr.length; i++)
{
if(arr[i].id === val || arr[i].parentId === arr[index].id)
{
resultArray.push(arr[i]);
}
}
return resultArray;
}
const result = deleteId(1);
console.log(result);
Essentially the same idea as #Barmar, but done in a different way :)
const arr = [
{title: 'one', id: 1, parentId: null},
{title: 'two', id: 2, parentId: 1},
{title: 'three', id: 3, parentId: null},
{title: 'four', id: 4, parentId: 2},
{title: 'five', id: 5, parentId: 2},
{title: 'six', id: 6, parentId: 5},
{title: 'seven', id: 7, parentId: 3},
];
function cascadingDelete(arr, title) {
const index = arr.findIndex((e) => e.title === title);
const indices = [index].concat((function walk(parentId) {
const children = arr.filter((e) => e.parentId === parentId);
const indices = children.map((c) => arr.indexOf(c));
return indices.concat(children.map((c) => walk(c.id)));
})(arr[index].id)).flat(Infinity);
return arr.filter((_, i) => !indices.includes(i));
}
console.log(cascadingDelete(arr, "one"));
Related
i have an parent array of objects
const parent = [{name: 'one', id: 1}, {name: 'two', id: 2}, {name: 'three', id: 3}, {name: 'four', id: 4}, {name: 'five', id: 5}]
i have made sub arrays out of it
const childrenCollection = [[{name: 'one', id: 1}, {name: 'two', id: 2}], [{name: 'three', id: 3}, {name: 'four', id: 4}], [{name: 'five', id: 5}]]
now i am looping through the childrenCollection
childrenCollection.map(childrenSubCollection => {
// find the actual index from the main parent array
const indexOfOne = childrenSubCollection.findIndex(childrenSub => {
return childrenSub[0].id === // ??
})
const indexOfTwo = childrenSubCollection.findIndex(childrenSub => {
return childrenSub[1].id === // ??
})
console.log(childrenSubCollection[0], childrenSubCollection[1], 'index', indexOfOne, indexOfTwo )
})
How can i find the index of each from parent array
Using Array#findIndex:
const
parent = [ { name: 'one', id: 1 }, { name: 'two', id: 2 }, { name: 'three', id: 3 }, { name: 'four', id: 4 }, { name: 'five', id: 5 } ],
childrenCollection = [
[ { name: 'one', id: 1 }, { name: 'two', id: 2 } ],
[ { name: 'three', id: 3 }, { name: 'four', id: 4 } ],
[ { name: 'five', id: 5 } ]
];
childrenCollection.forEach(([ one, two ]) => {
const indexOfOne = one ? parent.findIndex(item => one.id === item.id) : -1;
const indexOfTwo = two ? parent.findIndex(item => two.id === item.id) : -1;
console.log(one, two, 'index', indexOfOne, indexOfTwo);
});
Using Map:
const
parent = [ { name: 'one', id: 1 }, { name: 'two', id: 2 }, { name: 'three', id: 3 }, { name: 'four', id: 4 }, { name: 'five', id: 5 } ],
childrenCollection = [
[ { name: 'one', id: 1 }, { name: 'two', id: 2 } ],
[ { name: 'three', id: 3 }, { name: 'four', id: 4 } ],
[ { name: 'five', id: 5 } ]
];
const indexMap = new Map( parent.map(({ id }, index) => ([ id, index ])) );
childrenCollection.forEach(([ one, two ]) => {
const indexOfOne = one ? indexMap.get(one.id) : -1;
const indexOfTwo = two ? indexMap.get(two.id) : -1;
console.log(one, two, 'index', indexOfOne, indexOfTwo);
});
I am struggling to find out a solution to my problem, but at the moment I cannot come up with the right one.
When I have my two arrays of objects I want to filter based on category IDs and extract the data from the second one into a new array for example :
const array1 = [
{id: 1, name: 'Tropical'},
{id: 2, name: 'Common'}
]
const array2 = [
{id:1, name: 'Banana', category_id: 1},
{id:2, name: 'Mango', category_id: 1},
{id:3, name: 'Apple', category_id: 2},
]
And when click happens I detect the first ID and render the new array only with data that matches the ID.
Click Tropical
New array :
[
{id:1, name: 'Banana', category_id: 1},
{id:2, name: 'Mango', category_id: 1},
]
I would be happy if someone give me a hint on how can I tackle this problem. Thanks !
Correct me if I am wrong, So you need a function that received a categoryId and you need to filter out array2 based on that category_id
You can try this
const array1 = [{
id: 1,
name: 'Tropical'
},
{
id: 2,
name: 'Common'
}
]
const array2 = [{
id: 1,
name: 'Banana',
category_id: 1
},
{
id: 2,
name: 'Mango',
category_id: 1
},
{
id: 3,
name: 'Apple',
category_id: 2
},
]
function categoryFruits(categoryId) {
return array2.filter(obj => obj.id === categoryId)
}
console.log(categoryFruits(3));
Use reduce to map over each item in array1 and filter to grab the items of that category_id
const array1 = [{
id: 1,
name: 'Tropical'
},
{
id: 2,
name: 'Common'
}
]
const array2 = [{
id: 1,
name: 'Banana',
category_id: 1
},
{
id: 2,
name: 'Mango',
category_id: 1
},
{
id: 3,
name: 'Apple',
category_id: 2
},
]
const obj = array1.reduce((acc, cur) => {
acc[cur.name] = array2.filter(v => v.category_id === cur.id)
return acc
}, {})
console.log(obj)
You could do something like filtering array2 and taking all the elements that have Tropical as name in array1.
const array1 = [
{id: 1, name: 'Tropical'},
{id: 2, name: 'Common'}
]
const array2 = [
{id:1, name: 'Banana', category_id: 1},
{id:2, name: 'Mango', category_id: 1},
{id:3, name: 'Apple', category_id: 2},
]
// take tropical friuts
let tropicalFriuts = array2.filter(x => x.category_id === array1.filter(y => y.name === 'Tropical')[0].id);
console.log(tropicalFriuts);
If I understood your problem you want before find the id, based on the name of the category, and later filter array2 data based on this id.
const array1 = [{
id: 1,
name: 'Tropical'
},
{
id: 2,
name: 'Common'
}
]
const array2 = [{
id: 1,
name: 'Banana',
category_id: 1
},
{
id: 2,
name: 'Mango',
category_id: 1
},
{
id: 3,
name: 'Apple',
category_id: 2
},
]
const id_key = array1.find(item=>item.name === 'Tropical').id;
const result = array2.filter(item=>item.category_id === id_key);
console.log(result);
Hi i need some help to update the userSettings variable, for example if i remove a product of the products array, i need to update the sortedProducts array of the userSettings.categories array, i'm trying with nested for loops but i would like to improve performance with functional array methods. This is what i've been trying, thanks in advance community.
let products = [
{id: 1, name: 'Brasilian', category: 'cofee'},
{id: 2, name: 'Colombian', category: 'cofee'},
{id: 3, name: 'Apple', category: 'fruit'},
{id: 4, name: 'Strawberry', category: 'fruit'},
{id: 5, name: 'Banana', category: 'fruit'},
{id: 6, name: 'Pepper', category: 'spices'},
{id: 7, name: 'Salt', category: 'spices'}
]
let userSettings = {
categories: [
{name: 'fruit', sortedProducts: [5, 3, 4]},
{name: 'spices', sortedProducts: []},
{name: 'cofee', sortedProducts: []},
]
}
// lets remove the strawberry product
products.splice(3, 1);
console.log(products);
// i need to update userSettings
const updateUserSettings = (() => {
for(let i = 0; i < userSettings.categories.length; i++){
if(userSettings.categories[i].sortedProducts.length){
console.log(userSettings.categories[i].sortedProducts);
for(let j = 0; j < products.length; j++){
if(products[j].category == userSettings.categories[i] && !userSettings.categories[i].sortedProducts.includes(products[j].id)){
console.log('no includes')
}
}
}
}
})();
expectedOutput = {
categories: [
{name: 'fruit', sortedProducts: [5, 3]},
{name: 'spices', sortedProducts: []},
{name: 'cofee', sortedProducts: []},
]
}
Since the other categories need to have empty arrays, the best way would be to remove any existing sortedProducts in userSettings that no longer exist in products.
userSettings.categories.forEach(category => {
// get the product ids for the category
let filteredProductIds = products.filter(product => product.category === category.name)
.map(product => product.id)
// remove any id that no longer exists in products from sortedProducts
category.sortedProducts = category.sortedProducts.filter(sortedProduct => filteredProductIds.includes(sortedProduct))
})
I have an original array and I want to plot it in Sunburst map which needs a hierarchical data structure.
[
{id: "Asia,India,NewDelhi", value: 41},
{id: "Europe,Germany,Berlin", value: 24},
{id: "Europe,England,London", value: 3},
{id: "NorthAmerica,USA,NewYork", value: 4},
{id: "NorthAmerica,USA,Boston", value: 3},
{id: "NorthAmerica,USA,chicago", value: 3},
{id: "Austrailia,Sydney", value: 4},
{id: "Asia,China,Beijing", value: 2},
]
Desired Result
[
{
id: Asia,
children:[{
id: India,
children:[{
id: Delhi,
value: 41,
}]
},
{
id:China,
children:[{
id: Beijing
value: 2,
}]
}]
},
{
id: Europe,
children: [{
id: Germany,
children: [{
id: Berlin,
value: 24,
}]
},
{
id: England,
children: [{
id: London,
value: 3,
}]
}]
},
{
id: NorthAmerica,
children:[{
id: USA,
children:[{
id: NewYork,
value: 4,
},
{
id: Boston,
value: 3,
},
{
id: Chicago,
value: 3,
}]
}]
},
{
id: Austrailia
children: [{
id:Sydney,
value: 4,
}]
},
]
can anyone help me with this, I tried using reduce method but I am not able to get the desired result.
PS : It would be super useful if anyone could suggest an answer that would deal with n number of ids separated by commas. For ex: here we have 3 id hierarchy separated by commas, what would happen if there were 4 or 5 depth data.
A simple solution with recursion:
const data = [
{id: "Asia,India,NewDelhi", value: 41},
{id: "Europe,Germany,Berlin", value: 24},
{id: "Europe,England,London", value: 3},
{id: "NorthAmerica,USA,NewYork", value: 4},
{id: "NorthAmerica,USA,Boston", value: 3},
{id: "NorthAmerica,USA,Chicago", value: 3},
{id: "Austrailia,Sydney", value: 4},
{id: "Asia,China,Beijing", value: 2},
];
const addChild = (ids, value, arr) => {
const id = ids.shift();
let index = arr.findIndex(item => item.id === id);
if (index < 0) {
arr.push({id, children: []});
index = arr.length - 1;
}
if (ids.length > 0) {
const children = arr[index].children;
addChild(ids, value, children);
}
else
arr[index].value = value;
}
const treeData = data.reduce((tree, item) => {
const ids = item.id.split(',');
addChild(ids, item.value, tree);
return tree;
}, []);
console.log(treeData);
To build a hierarchy of objects from your input is fairly straightforward, you dont even need to do anything recursive a loop + reduce will do it. This will work with any number of levels in your comma separated list.
const input = [
{id: "Asia,India,NewDelhi", value: 41},
{id: "Europe,Germany,Berlin", value: 24},
{id: "Europe,England,London", value: 3},
{id: "NorthAmerica,USA,NewYork", value: 4},
{id: "NorthAmerica,USA,Boston", value: 3},
{id: "NorthAmerica,USA,chicago", value: 3},
{id: "Austrailia,Sydney", value: 4},
{id: "Asia,China,Beijing", value: 2}
]
const result = input.map(o => ({ids:o.id.split(","), value:o.value})).reduce( (acc,obj) => {
let curr = acc;
let id;
while( (id = obj.ids.shift()) != null ){
if(!curr[id])
curr[id] = {};
curr = curr[id];
}
curr.value = obj.value
return acc;
},{});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
To then turn this into the format you wanted does take a bit of recursion:
const input = [
{id: "Asia,India,NewDelhi", value: 41},
{id: "Europe,Germany,Berlin", value: 24},
{id: "Europe,England,London", value: 3},
{id: "NorthAmerica,USA,NewYork", value: 4},
{id: "NorthAmerica,USA,Boston", value: 3},
{id: "NorthAmerica,USA,chicago", value: 3},
{id: "Austrailia,Sydney", value: 4},
{id: "Asia,China,Beijing", value: 2}
]
const result = input.map(o => ({ids:o.id.split(","), value:o.value})).reduce( (acc,obj) => {
let curr = acc;
let id;
while( (id = obj.ids.shift()) != null ){
if(!curr[id])
curr[id] = {};
curr = curr[id];
}
curr.value = obj.value
return acc;
},{});
function buildHierarchy(input){
return Object.entries(input).map( ([id,children]) => {
if(children.value){
return {id,value:children.value}
}
return {id, children: buildHierarchy(children)}
})
}
console.log(buildHierarchy(result));
.as-console-wrapper { max-height: 100% !important; top: 0; }
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);