Reputation: 2024
I want to iterate over a structure, push chosen nodes to an array and return all of them.
var structure = {
folder: getFolder(1, 'name1'),
children: [
{
folder: getFolder(2, 'name2'),
children: [
{
folder: getFolder(4, 'name2'),
children: []
}
]
},
{
folder: getFolder(3, 'name3'),
children: []
}
]
};
So for example, if folder node matches getFolder(x, 'name2'), I would get an array of two elements:
folder: getFolder(2, 'name2'),
children: [
{
folder: getFolder(4, 'name2'),
children: []
}
]
and
folder: getFolder(4, 'name2'),
children: []
Because both match the given criteria. The function I came up with is:
var searchAll = function (data, searchFor, results) {
results = results || [];
if (data[searchFor.type] != undefined &&
data[searchFor.type][searchFor.index].indexOf(searchFor.value) !== -1) {
return data;
}
if (data.children != null) {
var result = null;
for (var i = 0; result == null && i < data.children.length; i++) {
results.push(searchAll(data.children[i], searchFor, results));
}
}
return results;
};
searchAll(structure, {
type: 'folder',
index: 'name',
value: 'name2'
});
But it returns undefined. How should I do this?
Upvotes: 1
Views: 5896
Reputation: 7913
The key to building up an array with recursion is the concat() method, which will properly return a copy of the array all the way up the recursion stack.
In the example below, objects that match your criteria get added in with push(), while child objects are searched through recursively and their results are concatenated to the result array. For simplicity I used the results of what your getFolder()
function would return in the data:
var structure = {
folder: {id:1, name:'name1'}, //getFolder(1, 'name1'),
children: [{
folder: {id:2, name:'name2'}, //getFolder(2, 'name2'),
children: [{
folder: {id:4, name:'name2'}, //getFolder(4, 'name2'),
children: []
}]
}, {
folder: {id:3, name:'name3'}, //getFolder(3, 'name3'),
children: []
}]
};
function searchAll(object, criteria) {
var i, j, result = [];
for (i in object) {
if (i === criteria.type && object[i][criteria.index] === criteria.value) {
result.push(object);
} else if (i === 'children' && object[i].length > 0) {
for (j = 0; j < object[i].length; j++) {
result = result.concat(searchAll(object[i][j], criteria));
}
}
}
return result;
}
console.log(searchAll(structure, {type: 'folder', index: 'name', value: 'name2'}));
Edit: link to JSFiddle because it looks like the SO code snippet stops the recursion, the results should be correct (2 objects with the data you wanted) https://jsfiddle.net/fswmxk7h/
Upvotes: 1
Reputation: 4002
The main issue is that you are bailing out and not appending to results when you find a match (return data;
). This is a hopefully simpler and working version. You also have a problem that you are both passing 'results' to the recursion, and pushing the resulting array onto results, which isn't what you wanted. This is hopefully correct for the style you are going for (untested):
var searchAll = function (data, searchFor, results) {
results = results || [];
if (data[searchFor.type] &&
data[searchFor.type][searchFor.index].indexOf(searchFor.value) !== -1) {
results.push(data); // now carry on an recurse children
}
if (data.children) {
for (var i = 0; i < data.children.length; i++) {
// use results arg to avoid array creation on recursion:
searchAll(data.children[i], searchFor, results);
}
}
return results;
};
searchAll(structure, {
type: 'folder',
index: 'name',
value: 'name2'
});
Upvotes: 0