tree search using recursion javascript - javascript

I am looking for a way to be able to search in an array, with nested arrays, a node with information. It can be seen as a tree
const data = [
{
id: '1-1',
name: "Factory",
children: [
{
id: '1-1-1',
name: "Areas",
children: [
{
id: '1-1-1-1',
name: "Sales",
children: [
{
id: '1-1-1-1-1',
name: "Bill Gates",
children:[...]
},
...
]
},
...
]
},
...
],
},
...
]
If I wanted to find the node with name: Bill Gates
Try this function, but it does not work properly
const getElements = (treeData, text) => {
return treeData.map(node => {
const textMatch = node.name.toLowerCase().includes(text.toLowerCase());
if (textMatch) {
console.log(node);
return node;
} else {
if (node.children) {
return getElements(node.children, text)
}
}
})
}
In deeper data like Bill Gates Node returns the entire TreeArray, but with all the data that does not contain the name Bill Gates as undefined

You probably don't want to use .map here, because you don't want a mutated array, you just want to find a node. Using a for loop gets the expected result:
const data = [{
id: '1-1',
name: "Factory",
children: [
{
id: '1-1-1',
name: "Areas",
children: [
{
id: '1-1-1-1',
name: "Sales",
children: [
{
id: '1-1-1-1-1',
name: "Bill Gates",
children:[]
},
]
},
]
},
]
}];
const getElements = (treeData, text) => {
for (let i=0, node = treeData[i]; node; i++) {
const textMatch = node.name.toLowerCase().includes(text.toLowerCase());
if (textMatch) {
console.log(node);
return node;
} else if (node.children) {
return getElements(node.children, text)
}
}
};
getElements(data, 'Bill Gates');

Related

How do I remove object (element) in Javascript object?

How do I remove an element from a JavaScript object by ID?
For instance I have to remove 004 or 007:
const obj = {
id: '001',
children: [
{
id: '002',
children: [
{
id: '003',
children: [],
},
{
id: '004',
children: [
{
id: '005',
children: [],
}
],
}
],
},
{
id: '006',
children: [
{
id: '007',
children: [],
}
],
},
]
}
i am trying to like this, find id but what should be next. It is expected to remove id from the object.
const removeById = (obj = {}, id = '') => {
console.log('obj: ', obj)
const search = obj.children.find(o => o.id === id)
console.log('##search: ', search)
if(search) {
console.log('## parent id: ', obj.id)
...
}
if (obj.children && obj.children.length > 0) {
obj.children.forEach(el => removeById(el, id));
}
}
removeById(obj, '007')
You can use findIndex to get the location in the array.
To remove an element from an array you need to use splice.
You can then loop over the children with some and check the children's children. Using some, you can exit out when you find the id so you do not have to keep looping.
let obj = {
id: '001',
children: [
{
id: '002',
children: [
{
id: '003',
children: [],
},
{
id: '004',
children: [
{
id: '005',
children: [],
}
],
}
],
},
{
id: '006',
children: [
{
id: '007',
children: [],
}
],
},
]
}
const removeById = (parentData, removeId) => {
// This is only the parent level!
// If you will never delete the first level parent, this is not needed
if (parentData.id === removeId){
Object.keys(data).forEach(key => delete parentData[key]);
return true;
}
function recursiveFind (children) {
// check if any of the children have the id
const index = children.findIndex(({id}) => id === removeId);
// if we have an index
if (index != -1) {
// remove it
children.splice(index, 1);
// say we found it
return true;
}
// Loop over the chldren check their children
return children.some(child => recursiveFind(child.children));
}
return recursiveFind(parentData.children);
}
removeById(obj,'004');
removeById(obj,'007');
console.log(obj)
We can use a recursive function to do it
let obj = {
id: '001',
children: [
{
id: '002',
children: [
{
id: '003',
children: [],
},
{
id: '004',
children: [
{
id: '005',
children: [],
}
],
}
],
},
{
id: '006',
children: [
{
id: '007',
children: [],
}
],
},
]
}
const removeById = (data,id) =>{
if(data.id === id){
delete data.id
delete data.children
return
}
data.children.forEach(d =>{
removeById(d,id)
})
}
removeById(obj,'006')
console.log(obj)
Update: not leave an empty object after removing
let obj = {
id: '001',
children: [
{
id: '002',
children: [
{
id: '003',
children: [],
},
{
id: '004',
children: [
{
id: '005',
children: [],
}
],
}
],
},
{
id: '006',
children: [
{
id: '007',
children: [],
}
],
},
]
}
let match = false
const removeById = (data,id) =>{
match = data.some(d => d.id == id)
if(match){
data = data.filter(d => d.id !== id)
}else{
data.forEach(d =>{
d.children = removeById(d.children,id)
})
}
return data
}
let data = [obj]
console.log(removeById(data,'004'))

How to filter a nested object and transform the resut at the same time?

I have a tree structure like this:
const tree = [
{
slug: 'item-1',
children: [
{
slug: 'item-1-1',
children: [
{ slug: 'item-1-1-1' },
{ slug: 'item-1-1-2' }
],
},
],
},
{
slug: 'item-2',
children: [
{
slug: 'item-2-1',
children: [
{ slug: 'item-2-1-1' },
{ slug: 'item-2-1-2' }
],
},
],
},
];
I want to filter it based on the slug which is not hard. I've seen some recursive solutions on StackOverflow. But I want only the direct children of the item to be in the result. For example, if I search for slug === "item-1", the result should be:
[
{
slug: "item-1"
children: [
slug: "item-1-1"
],
},
]
Maybe I can combine filter() and reduce() or even map() and somehow solve this but it doesn't seem optimal. The tree is likely to be big and complex. How would you solve this?
Here's a recursive approach. Basically, we are doing a breadth-first search, firstly we search at a level, and if the required item is found return the result after modifying the children array. If the key is not found in that level search the children.
const tree = [
{
slug: 'item-1',
children: [
{
slug: 'item-1-1',
children: [{ slug: 'item-1-1-1' }, { slug: 'item-1-1-2' }],
},
],
},
{
slug: 'item-2',
children: [
{
slug: 'item-2-1',
children: [{ slug: 'item-2-1-1' }, { slug: 'item-2-1-2' }],
},
],
},
];
const search = (data, key) => {
if (!data?.length) return null;
const children = [];
let found = null;
data.forEach((obj) => {
if (obj.slug === key) {
found = obj;
}
children.push(...(obj.children || []));
});
return found
? {
slug: key,
...(found.children?.length && {
children: found.children.map(({ slug }) => ({ slug })),
}),
}
: search(children, key);
};
console.log(search(tree, 'item-1-1'));

Could use someone's expertise with filtering my nested array by a certain value

I know there are a ton of "How to filter my array" questions. However, I'm still stuck.
I'm not too familiar with arrays and I'm hoping someone can help me with my particular situation.
I have an array that I'm trying to filter. I'm looking to return all data if "user.name" or "writers.name" equals "Adam". "user" will only ever have one name, while "writers" can have multiple.
[{
mediaId: '86699',
user: {
name: 'Adam',
id: '17622'
},
nodeType: 'testNode',
title: 'testTitle',
id: '23968',
writers: []
},
{
mediaId: '90547',
user: {
name: 'Jake',
id: '65936'
},
nodeType: 'testNode',
title: 'testTitleTwo',
id: '89960',
writers: [{
name: 'Adam',
id: '17622'
},
{
name: 'Steve',
id: '47622'
}]
},
{
mediaId: '99662',
user: {
name: 'James',
id: '22236'
},
nodeType: 'testNode',
title: 'testTitleThree',
id: '89960',
writers: [{
name: 'Paul',
id: '27622'
}]
}
]
Desired output:
[{
"mediaId": "86699",
"user": {
"name": "Adam",
"id": "17622"
},
"nodeType": "testNode",
"title": "testTitle",
"id": "23968",
"writers": []
},
{
"mediaId": "90547",
"user": {
"name": "Jake",
"id": "65936"
},
"nodeType": "testNode",
"title": "testTitleTwo",
"id": "89960",
"writers": [{
"name": "Adam",
"id": "17622"
},
{
"name": "Steve",
"id": "45389"
}]
}
]
Thank you in advance!!!
UPDATED: Everything is the same except that user and writers are now wrapped around properties.
[{
mediaId: '86699',
nodeType: 'testNode',
title: 'testTitle',
id: '23968',
properties: {
user: {
name: 'Adam',
id: '17622'
},
writers: []
}
}, {
mediaId: '90547',
nodeType: 'testNode',
title: 'testTitleTwo',
id: '89960',
properties: {
user: {
name: 'Jake',
id: '65936'
},
writers: [{
name: 'Adam',
id: '17622'
}, {
name: 'Steve',
id: '47622'
}]
}
}, {
mediaId: '99662',
nodeType: 'testNode',
title: 'testTitleThree',
id: '89960',
properties: {
user: {
name: 'James',
id: '22236'
},
writers: [{
name: 'Paul',
id: '27622'
}]
}
}]
To apply a filter, you are essentially iterating over each item in the array and seeing if it matches your criteria. Return true if you want it to stay, and return false if you want it to be filtered out.
So at the top-most layer, you have an array of objects, each of which appears to represent a node (judging by the "nodeType" property). We start with this:
const matches = array.filter(node => {
// criteria go here
return true; // or false
});
What are the criteria? Well it should remain if...
The node.user.name is "Adam"
Of the writers, at least one has writer.name === "Adam"
Let's define those.
const hasUserNameAdam = (node) => node?.user?.name === "Adam";
const hasWriterNameAdam = (writer) => writer?.name === "Adam";
We can do the same thing with the writer array to handle the second layer.
const arrayHasWriterNameAdam = writerArray.some(writer => hasWriterNameAdam(writer));
Now we just need to plug them in.
const matches = data.filter(node => {
const hasUserNameAdam = (node) => node?.user?.name === "Adam";
const hasWriterNameAdam = (writer) => writer?.name === "Adam";
const arrayHasWriterNameAdam = (writerArray) => writerArray.some(writer => hasWriterNameAdam(writer));
return hasUserNameAdam(node) || arrayHasWriterNameAdam(node?.writers);
});
And we can choose to refactor those inner functions back to being in line to clean things up, if we would like.
const matches = data.filter(node => node?.user?.name === "Adam" || node?.writers?.some(writer => writer?.name === "Adam"));
Update
Or with the updated object shape, the user and writers props are nested under the properties prop, so we only have to add the .properties layer to everywhere we access the user and writers properties to adapt.
const matches = data.filter(node => node?.properties?.user?.name === "Adam" || node?.properties?.writers?.some(writer => writer?.name === "Adam"));
input.filter(element => element.user.name === 'Adam' || element.writers.some(writer => writer.name === 'Adam'));
Try this:
const findAdam = arr => arr.filter(
element => element.properties.user.name === 'Adam' || element.properties.writers.some(writer => writer.name === 'Adam')
)

How to get nested parent path from a JSON tree in JavaScript?

I have a JSON tree structure like this.
[
{
"title":"News",
"id":"news"
},
{
"title":"Links",
"id":"links",
"children":[
{
"title":"World",
"id":"world",
"children":[
{
"title":"USA",
"id":"usa",
"children":[
{
"title":"Northeast",
"id":"northeast"
},
{
"title":"Midwest",
"id":"midwest"
}
]
},
{
"title":"Europe",
"id":"europe"
}
]
}
]
}
]
What i want is when i pass "northeast" to a function() it should return me the dot notation string path from the root. Expected return string from the function in this case would be "links.world.usa.northeast"
You could test each nested array and if found, take the id from every level as path.
const pathTo = (array, target) => {
var result;
array.some(({ id, children = [] }) => {
if (id === target) return result = id;
var temp = pathTo(children, target)
if (temp) return result = id + '.' + temp;
});
return result;
};
var data = [{ title: "News", id: "news" }, { title: "Links", id: "links", children: [{ title: "World", id: "world", children: [{ title: "USA", id: "usa", children: [{ title: "Northeast", id: "northeast" }, { title: "Midwest", id: "midwest" }] }, { title: "Europe", id: "europe" }] }] }];
console.log(pathTo(data, 'northeast'));

Nested array filtering, is there a more elegant way?

I'm trying to filter a nested structure, based on a search string.
If the search string is matched in an item, then I want to keep that item in the structure, along with its parents.
If the search string is not found, and the item has no children, it can be discounted.
I've got some code working which uses a recursive array filter to check the children of each item:
const data = {
id: '0.1',
children: [
{
children: [],
id: '1.1'
},
{
id: '1.2',
children: [
{
children: [],
id: '2.1'
},
{
id: '2.2',
children: [
{
id: '3.1',
children: []
},
{
id: '3.2',
children: []
},
{
id: '3.3',
children: []
}
]
},
{
children: [],
id: '2.3'
}
]
}
]
};
const searchString = '3.3';
const filterChildren = (item) => {
if (item.children.length) {
item.children = item.children.filter(filterChildren);
return item.children.length;
}
return item.id.includes(searchString);
};
data.children = data.children.filter(filterChildren);
console.log(data);
/*This outputs:
{
"id": "0.1",
"children": [
{
"id": "1.2",
"children": [
{
"id": "2.2",
"children": [
{
"id": "3.3",
"children": []
}
]
}
]
}
]
}*/
I'm concerned that if my data structure becomes massive, this won't be very efficient.
Can this be achieved in a 'nicer' way, that limits the amount of looping going on? I'm thinking probably using a reducer/transducer or something similarly exciting :)
A nonmutating version with a search for a child.
function find(array, id) {
var child,
result = array.find(o => o.id === id || (child = find(o.children, id)));
return child
? Object.assign({}, result, { children: [child] })
: result;
}
const
data = { id: '0.1', children: [{ children: [], id: '1.1' }, { id: '1.2', children: [{ children: [], id: '2.1' }, { id: '2.2', children: [{ id: '3.1', children: [] }, { id: '3.2', children: [] }, { id: '3.3', children: [] }] }, { children: [], id: '2.3' }] }] },
searchString = '3.3',
result = find([data], searchString);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories

Resources