Related
I'm attempting to write a function to recursively delete nodes based on Parent-child relationships.
function recursiveDelete(parentNode, deleteStack) {
let toDelete = allNodes.filter(function (node) {
return node.Parent !== undefined && node.Parent[0] === parentNode.ID;
})
toDelete.forEach((childNode) => {
deleteStack.push(...recursiveDelete(childNode, toDelete));
});
return deleteStack;
}
When I run this code, it create arrays with multiple copies of each node to be deleted, which seems to correspond to the height of the tree. It still seems to be running in polynomial time, but it isn't ideal.
edit - below is an example of what gets returned when I run the function on allNodes, with the duplicates.
[
{
"ID": "recymaQKcdrGzAqRM",
"Name": "Depth 1",
"Type": "task",
"Duration": 0.1,
"Priority": "6",
"Parent": [
"recQT9BPQtqs7Cg0U"
],
"Predecessors": [
"recKwlRnVhKik3SZF"
],
"depth": 2
},
{
"ID": "recRcn1t2Nkupcb9L",
"Name": "Start",
"Type": "start",
"Duration": 0.1,
"Priority": "5",
"Parent": [
"recymaQKcdrGzAqRM"
]
},
{
"ID": "recBmEBC1bo1CcJ8i",
"Name": "Depth 2",
"Type": "task",
"Duration": 0.1,
"Priority": "5",
"Parent": [
"recymaQKcdrGzAqRM"
],
"Predecessors": [
"recRcn1t2Nkupcb9L"
]
},
{
"ID": "recRcn1t2Nkupcb9L",
"Name": "Start",
"Type": "start",
"Duration": 0.1,
"Priority": "5",
"Parent": [
"recymaQKcdrGzAqRM"
]
},
{
"ID": "recBmEBC1bo1CcJ8i",
"Name": "Depth 2",
"Type": "task",
"Duration": 0.1,
"Priority": "5",
"Parent": [
"recymaQKcdrGzAqRM"
],
"Predecessors": [
"recRcn1t2Nkupcb9L"
]
},
{
"ID": "rec7qUgu7kpnQ06s7",
"Name": "Start",
"Type": "start",
"Duration": 0.1,
"Priority": "5",
"Parent": [
"recBmEBC1bo1CcJ8i"
]
},
{
"ID": "rec5Lyx3zuAsCPv9W",
"Name": "Depth 3",
"Type": "task",
"Duration": 0.1,
"Priority": "5",
"Parent": [
"recBmEBC1bo1CcJ8i"
],
"Predecessors": [
"rec7qUgu7kpnQ06s7"
]
},
{
"ID": "rec7qUgu7kpnQ06s7",
"Name": "Start",
"Type": "start",
"Duration": 0.1,
"Priority": "5",
"Parent": [
"recBmEBC1bo1CcJ8i"
]
},
{
"ID": "rec5Lyx3zuAsCPv9W",
"Name": "Depth 3",
"Type": "task",
"Duration": 0.1,
"Priority": "5",
"Parent": [
"recBmEBC1bo1CcJ8i"
],
"Predecessors": [
"rec7qUgu7kpnQ06s7"
]
}
]
My current hack is to just remove duplicates:
[...new Set(recursiveDelete(allNode, [allNode]))]
But I'd like to figure out what I'm doing wrong. Thanks!
edit, I think I figured it out. Instead of passing toDelete to the recursive function, I should've only been passing [childNode], so the correct code looks like this:
function recursiveDelete(parentNode, deleteStack) {
let toDelete = allNodes.filter(function (node) {
return node.Parent !== undefined && node.Parent[0] === parentNode.ID;
});
toDelete.forEach((childNode) => {
deleteStack.push(...recursiveDelete(childNode, [childNode]));
});
return deleteStack;
}
I have a multidimensional array of JSON data with 'n' number of nested children. My task is to add UniqueId to parent JSON and that uniqueId should be added as parentId to the child. Can you please help in javascript. Thanks
Note:
The number of child its recursive, and there can be any number of children. For this purpose, we can have a deep level of three
Input :
[{
"text": 1527978678434,
"value": 1527978678434,
"children": [{
"text": 1292232152442,
"value": 1292232152442,
"children": [{
"text": 474194771845,
"value": 474194771845,
"children": []
},
{
"text": 468086178830,
"value": 468086178830,
"children": []
}
]
},
{
"text": 1067869237589,
"value": 1067869237589,
"children": []
},
{
"text": 1166591731429,
"value": 1166591731429,
"children": []
},
]
}]
The Required Output:
[{
"text": 1527978678434,
"value": 1527978678434,
"parentId": 0,
"uniqueId": 1,
"children": [{
"text": 1292232152442,
"value": 1292232152442,
"parentId": 1,
"uniqueId": 2,
"children": [{
"text": 474194771845,
"value": 474194771845,
"parentId": 2,
"uniqueId": 3,
"children": []
},
{
"text": 468086178830,
"value": 468086178830,
"parentId": 2,
"uniqueId": 4,
"children": []
}
]
},
{
"text": 1067869237589,
"value": 1067869237589,
"parentId": 1,
"uniqueId": 5,
"children": []
},
{
"text": 1166591731429,
"value": 1166591731429,
"parentId": 1,
"uniqueId": 6,
"children": []
},
{
"text": 111221786011,
"value": 111221786011,
"parentId": 1,
"uniqueId": 7,
"children": []
},
{
"text": 641372005975,
"value": 641372005975,
"parentId": 1,
"uniqueId": 8,
"children": [{
"text": 23082640100,
"value": 23082640100,
"parentId": 8,
"uniqueId": 9,
"children": []
}]
}
]
}]
Consider data is your array.
Just do the DFS and alter the data on the fly. + Having a global to hold the increased ID.
let lastUniqueId = 0;
function addIds(children, parentId) {
(children || []).forEach(r => {
r.parentId = parentId;
r.uniqueId = ++lastUniqueId;
addIds(r.children, r.uniqueId);
});
}
addIds(data, lastUniqueId);
This seems something you can do using a recursive function while keeping track of a shared uniqueId counter, assuming the nesting doesn't go incredibly deep:
const data = [{
"text": 1527978678434,
"value": 1527978678434,
"children": [{
"text": 1292232152442,
"value": 1292232152442,
"children": [{
"text": 474194771845,
"value": 474194771845,
"children": []
},
{
"text": 468086178830,
"value": 468086178830,
"children": []
}
]
},
{
"text": 1067869237589,
"value": 1067869237589,
"children": []
},
{
"text": 1166591731429,
"value": 1166591731429,
"children": []
},
]
}];
function convert(array) {
let uniqueId = 0;
function mapData(data, parentId) {
const myId = ++uniqueId;
const result = {
...data, // copy all data
uniqueId, parentId, // add our new fields
// and handle the children recursively
children: data.children.map(c => mapData(c, myId)),
};
// Don't mind this, just tricking JS in displaying the children array at the bottom during console.log
const children = result.children; delete result.children; result.children = children;
return result;
}
return array.map(d => mapData(d, 0));
}
const result = convert(data);
console.log(result);
I Need to find multiple elements in a nested array and return it as an array.
I use the reduce function to find the elements, but it returns only one record.
Nested array:
{
"type": "group",
"level": 0,
"expand": "-closed",
"selected": false,
"text": "Федулов Владислав Владиславович",
"phoneNumber": "+7 (927) 999 9999",
"email": "qweeqwe#mail.ru",
"id": 24,
"parent": null,
"cardType": 0,
"childrens": [
{
"type": "group",
"level": 1,
"expand": "-closed",
"selected": false,
"text": "Ширяев Феликс Богуславович",
"phoneNumber": "+7 (123) 456 7810",
"email": "test#test.ru",
"id": 47,
"parent": 24,
"cardType": 0,
"childrens": [
{
"type": "manager",
"level": 2,
"expand": "-empty",
"selected": false,
"text": "Колесова Анастасия Олеговна",
"phoneNumber": "+7 (900) 000 0001",
"email": "eprosvirina#baccasoft.ru",
"id": 58,
"parent": 47,
"cardType": 0,
"childrens": null
}
]
}
]
},
{
"type": "group",
"level": 0,
"expand": "-closed",
"selected": false,
"text": "Игнатьева Женевьева Павловна",
"phoneNumber": "+7 (777) 777 7777",
"email": "igp#sks.ru",
"id": 3,
"parent": null,
"cardType": 0,
"childrens": [
{
"type": "group",
"level": 1,
"expand": "-closed",
"selected": false,
"text": "Меретин Викентий Васильевич",
"phoneNumber": "+7 (917) 193 5222",
"email": "keshman#gmail.com",
"id": 2,
"parent": 3,
"cardType": 1,
"childrens": [
{
"type": "manager",
"level": 2,
"expand": "-empty",
"selected": false,
"text": "Климаков Алексей Александрович",
"phoneNumber": "+7 (903) 888 8888",
"email": "krenog#gmail.com",
"id": 20,
"parent": 2,
"cardType": 1,
"childrens": null
}
]
}
]
}
and the reduce function:
var array = store.managersTree.treeNodes;
var items = [];
const findItemNested = (arr, searchString, nestingKey) => (
arr.reduce((a, item) => {
if (a) return a;
if (item.text.indexOf(searchString)!==-1 ||
item.phoneNumber.indexOf(searchString)!==-1 ||
item.email.indexOf(searchString)!==-1) return item;
if (item[nestingKey]) return findItemNested(item[nestingKey],
searchString, nestingKey)
}, [])
);
const element = findItemNested(array, searchString, "childrens");
I am trying to find a record matching at least one criteria, expecting that reduce returns multiple records, but this returns only one record, despite there were multiple records found.
Any help would be appreciated.
UPD: searchString could be string like phoneNumber, text or email
You could reduce the array recursively.
Destructure the object in reduce parameter to get nestingKey and other properties separately
Create an array of filterKeys which have the keys which you want to search for searchString.
Use some to check if any of the fields in the object have a value which includes the searchString.
If nestingKey exists, you can push the nested items to the accumulator array.
const input=[{"type":"group","level":0,"expand":"-closed","selected":false,"text":"Федулов Владислав Владиславович","phoneNumber":"+7 (927) 999 9999","email":"qweeqwe#mail.ru","id":24,"parent":null,"cardType":0,"childrens":[{"type":"group","level":1,"expand":"-closed","selected":false,"text":"Ширяев Феликс Богуславович","phoneNumber":"+7 (123) 456 7810","email":"test#test.ru","id":47,"parent":24,"cardType":0,"childrens":[{"type":"manager","level":2,"expand":"-empty","selected":false,"text":"Колесова Анастасия Олеговна","phoneNumber":"+7 (900) 000 0001","email":"eprosvirina#baccasoft.ru","id":58,"parent":47,"cardType":0,"childrens":null}]}]},{"type":"group","level":0,"expand":"-closed","selected":false,"text":"Игнатьева Женевьева Павловна","phoneNumber":"+7 (777) 777 7777","email":"igp#sks.ru","id":3,"parent":null,"cardType":0,"childrens":[{"type":"group","level":1,"expand":"-closed","selected":false,"text":"Меретин Викентий Васильевич","phoneNumber":"+7 (917) 193 5222","email":"keshman#gmail.com","id":2,"parent":3,"cardType":1,"childrens":[{"type":"manager","level":2,"expand":"-empty","selected":false,"text":"Климаков Алексей Александрович","phoneNumber":"+7 (903) 888 8888","email":"krenog#gmail.com","id":20,"parent":2,"cardType":1,"childrens":null}]}]}],
filterKeys = ["text", "phoneNumber", "email"];
function findItemNested(array, searchString, nestingKey) {
return array.reduce((acc, { [nestingKey]: nested, ...o }) => {
if (filterKeys.some(k => o[k] && o[k].includes(searchString)))
acc.push(o)
if (nested)
acc.push(...findItemNested(nested, searchString, nestingKey))
return acc;
}, [])
}
console.log(findItemNested(input, "keshman", "childrens"))
console.log(findItemNested(input, "#gmail.com", "childrens"))
Always Array.reduce returns a single value after processing each element in the array. If you want to return matching records in array then you can use, Array.filter,
[1,2,3,4,5].filter((element) => {
return (element === 2 || element ===4);
});
For this above code, the filtered array will be,
[2,4]
And before filter you should either flat the array or should traverse each and every element to filter the array instead of using Array.filter()
Without reduce also its possible,
var s = [{
"type": "group",
"level": 0,
"expand": "-closed",
"selected": false,
"text": "Федулов Владислав Владиславович",
"phoneNumber": "+7 (927) 999 9999",
"email": "qweeqwe#mail.ru",
"id": 24,
"parent": null,
"cardType": 0,
"childrens": [
{
"type": "group",
"level": 1,
"expand": "-closed",
"selected": false,
"text": "Ширяев Феликс Богуславович",
"phoneNumber": "+7 (123) 456 7810",
"email": "test#test.ru",
"id": 47,
"parent": 24,
"cardType": 0,
"childrens": [
{
"type": "manager",
"level": 2,
"expand": "-empty",
"selected": false,
"text": "Колесова Анастасия Олеговна",
"phoneNumber": "+7 (900) 000 0001",
"email": "eprosvirina#baccasoft.ru",
"id": 58,
"parent": 47,
"cardType": 0,
"childrens": null
},
{
"type": "group",
"level": 2,
"expand": "-empty",
"selected": false,
"text": "Колесова Анастасия Олеговна",
"phoneNumber": "+7 (900) 000 0001",
"email": "eprosvirina#baccasoft.ru",
"id": 534,
"parent": 47,
"cardType": 0,
"childrens": null
},
{
"type": "manager",
"level": 2,
"expand": "-empty",
"selected": false,
"text": "Колесова Анастасия Олеговна",
"phoneNumber": "+7 (900) 000 0001",
"email": "eprosvirina#baccasoft.ru",
"id": 523,
"parent": 47,
"cardType": 0,
"childrens": null
}
]
}
]
},
{
"type": "manager",
"level": 0,
"expand": "-closed",
"selected": false,
"text": "Игнатьева Женевьева Павловна",
"phoneNumber": "+7 (777) 777 7777",
"email": "igp#sks.ru",
"id": 3,
"parent": null,
"cardType": 0,
"childrens": [
{
"type": "group",
"level": 1,
"expand": "-closed",
"selected": false,
"text": "Меретин Викентий Васильевич",
"phoneNumber": "+7 (917) 193 5222",
"email": "keshman#gmail.com",
"id": 2,
"parent": 3,
"cardType": 1,
"childrens": [
{
"type": "manager",
"level": 2,
"expand": "-empty",
"selected": false,
"text": "Климаков Алексей Александрович",
"phoneNumber": "+7 (903) 888 8888",
"email": "krenog#gmail.com",
"id": 20,
"parent": 2,
"cardType": 1,
"childrens": null
}
]
}
]
}];
function filterRequiredElement(arr, searchString, nestingKey) {
arr.forEach((item, index) => {
if (item.type !== searchString || item.phoneNumber !== searchString || item.email !== searchString) {
arr.splice(index, 1);
}
});
for(let item of arr) {
if (item[nestingKey] !== null) {
filterRequiredElement(item[nestingKey], searchString, nestingKey);
}
}
}
filterRequiredElement(s ,'Климаков Алексей Александрович', 'childrens');
console.log(s);
I have below JavaScript with n level children and want to search for id and if any of item from has matching id than need to return object from root to matching item.
I want to return entire hierarchy of found item from root till object with it's children.
I tried with lodash and underscore and could not find easy solution.
input: {
"children": [{
"name": "Home",
"title": "Home",
"id": "home1",
"children": []
},
{
"name": "BUSINESS AND ROLE SPECIFIC",
"title": "BUSINESS AND ROLE SPECIFIC",
"id": "BAR1",
"children": [{
"name": "Global Businesses",
"title": "Global Businesses",
"id": "GB1",
"children": [{
"name": "Commercial Banking",
"title": "Commercial Banking",
"id": "CB1",
"children": [{
"name": "FLAGSHIP PROGRAMMES",
"title": "FLAGSHIP PROGRAMMES",
"id": "FG1",
"children": []
}]
}]
}]
},
{
"name": "RISK MANAGEMENT",
"title": "RISK MANAGEMENT",
"id": "RM1",
"children": []
}
]
}
Search: {
id: 'FG1'
}
return :{
"name": "BUSINESS AND ROLE SPECIFIC",
"title": "BUSINESS AND ROLE SPECIFIC",
"id": "BAR1",
"children": [{
"name": "Global Businesses",
"title": "Global Businesses",
"id": "GB1",
"children": [{
"name": "Commercial Banking",
"title": "Commercial Banking",
"id": "CB1",
"children": [{
"name": "FLAGSHIP PROGRAMMES",
"title": "FLAGSHIP PROGRAMMES",
"id": "FG1",
"children": [{}]
}]
}]
}]
}
You could use this function:
function findChild(obj, condition) {
if (Object.entries(condition).every( ([k,v]) => obj[k] === v )) {
return obj;
}
for (const child of obj.children || []) {
const found = findChild(child, condition);
// If found, then add this node to the ancestors of the result
if (found) return Object.assign({}, obj, { children: [found] });
}
}
// Sample data
var input = { "children": [{ "name": "Home", "title": "Home", "id": "home1", "children": [] }, { "name": "BUSINESS AND ROLE SPECIFIC", "title": "BUSINESS AND ROLE SPECIFIC", "id": "BAR1", "children": [{ "name": "Global Businesses", "title": "Global Businesses", "id": "GB1", "children": [{ "name": "Commercial Banking", "title": "Commercial Banking", "id": "CB1", "children": [{ "name": "FLAGSHIP PROGRAMMES", "title": "FLAGSHIP PROGRAMMES", "id": "FG1", "children": [] }] }] }] }, { "name": "RISK MANAGEMENT", "title": "RISK MANAGEMENT", "id": "RM1", "children": [] } ]},
search = { id: 'FG1' };
console.log(findChild(input, search));
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use this also for searching with multiple conditions, which must be true at the same time:
search = { "name": "Global Businesses", "title": "Global Businesses" };
... would give you the object that has the specified name and title.
Follow-up question
You asked in comments:
Is there way to supply number to not remove children for given node in input. like,
const donotRemoveChildNode = 2;
console.log(findChild(input, search, donotRemoveChildNode ));
...so it will not remove that specific node's children if it matches condition?
Here, if we search for { id: 'FG1'} and supply donotRemoveChildNode = 2, it would not remove the first level children for "Commercial banking".
I would say the donotRemoveChildNode would have to be 3, as there are three levels of children arrays in the ancestor-hierarchy of the "Commercial banking" node. A value of 0 would show the first level children of the top-most children property.
Here is how that extra argument would work -- I added some records to the data to illustrate the difference in the output:
function findChild(obj, condition, removeChildNodesBefore = Infinity) {
if (Object.entries(condition).every( ([k,v]) => obj[k] === v )) {
return obj;
}
for (const child of obj.children || []) {
let found = findChild(child, condition, removeChildNodesBefore - 1);
if (found) {
return Object.assign({}, obj, {
children: removeChildNodesBefore <= 0
? obj.children.map( sibling =>
sibling == child ? found
: Object.assign({}, sibling, {children: []})
)
: [found]
});
}
}
}
var input = { "children": [{ "name": "Home", "title": "Home", "id": "home1", "children": [] }, { "name": "BUSINESS AND ROLE SPECIFIC", "title": "BUSINESS AND ROLE SPECIFIC", "id": "BAR1", "children": [{ "name": "Global Businesses", "title": "Global Businesses", "id": "GB1", "children": [{ "name": "test", "title": "test", "id": "xxx", "children": [{ "name": "testDeep", "title": "test", "id": "deep", "children": []}]}, { "name": "Commercial Banking", "title": "Commercial Banking", "id": "CB1", "children": [{ "name": "test", "title": "test", "id": "yyy", "children": []}, { "name": "FLAGSHIP PROGRAMMES", "title": "FLAGSHIP PROGRAMMES", "id": "FG1", "children": [] }] }] }] }, { "name": "RISK MANAGEMENT", "title": "RISK MANAGEMENT", "id": "RM1", "children": [] } ]},
search = { id: 'FG1' }
console.log(findChild(input, search, 3));
.as-console-wrapper { max-height: 100% !important; top: 0; }
function getBranch(branches, leaf_id)
{
var result_branch = null;
branches.some(function(branch, idx) {
if (branch.id == leaf_id) {
result_branch = Object.assign({}, branch);
result_branch.children.forEach(function(child, idx) {
delete result_branch.children[idx].children;
});
return true;
} else {
let target_branch = getBranch(branch.children, leaf_id);
if (target_branch) {
result_branch = Object.assign({}, branch);
delete result_branch.children
result_branch.children = [target_branch];
return true;
}
}
return false;
});
return result_branch;
}
console.log(getBranch(input.children, 'GB1'));
One way is to first loop the root children, and then create another function to see if the Id exists in any of it's children.
var data = {
"children": [{
"name": "Home",
"title": "Home",
"id": "home1",
"children": []
},
{
"name": "BUSINESS AND ROLE SPECIFIC",
"title": "BUSINESS AND ROLE SPECIFIC",
"id": "BAR1",
"children": [{
"name": "Global Businesses",
"title": "Global Businesses",
"id": "GB1",
"children": [{
"name": "Commercial Banking",
"title": "Commercial Banking",
"id": "CB1",
"children": [{
"name": "FLAGSHIP PROGRAMMES",
"title": "FLAGSHIP PROGRAMMES",
"id": "FG1",
"children": []
}]
}]
}]
},
{
"name": "RISK MANAGEMENT",
"title": "RISK MANAGEMENT",
"id": "RM1",
"children": []
}
]
};
function hasId( id, data ) {
if (data.id === id) return true;
if (data.children) {
for (const child of data.children) {
if (hasId( id, child)) return true;
}
}
return false;
}
function search( id, data ) {
for (const child of data.children) {
if (hasId(id, child)) return child;
}
return null;
}
console.log(search( "FG1", data ));
I need to implement one collapsible diagram using d3. But first, it is necessary to parse my json file in order to create correct hierachies.
This is my json file example which I need transform:
{"likes":[{"_id":null,"category":"Music","link":"https://www.facebook.com/Brum","name":"Brum"},
{"_id":null,"category":"Music","link":"https://www.facebook.com/pan", "name":"Pan FM"},
{"_id":null,"category":"Music","link":"https://www.facebook.com/Example","name":"Example"},
{"_id":null,"category":"Books","link":"https://www.facebook.com/foo","name":"Foo"},
{"_id":null,"category":"Movies","link":"https://www.facebook.com/Titanic","name":"Titanic"}]}]}
And this the structure that I need extract from my json file:
var Data = [{
"name": "SocialProfiles",
"parent": "null",
"children": [{
"name": "Likes",
"parent": "SocialProfiles",
"children": [{
"name": "Music",
"parent": "Likes",
"children": [{
"parent": "Music",
"name": "Brum"
}, {
"parent": "Music",
"name": "Pan FM"
}, {
"parent": "Music",
"name": "Example"
}]
},
{
"name": "Books",
"parent": "Likes",
"children": [{
"parent": "Books",
"name": "Foo"
}, {
"parent": "Books",
"name": "Foo"
}]
}, {
"name": "Movies",
"parent": "Likes",
"children": [{
"parent": "Movies",
"name": "Titanic"
}]
}
]
}]
}];