let tree = {
name: "A",
children: [
{
name: 'A-1',
children: [
{name: "A-1-A"},
{name: "A-1-B"},
]
},
{
name: 'B-1',
children: [
{
name: "B-1-A",
children: [
{name: "B-11-A"},
{name: "B-11-B"}
]
},
{name: "B-1-B"},
]
},
]
};
I am trying to find object from tree object using recursion .
when I call like this searchFn(tree,'A-1') it should return {
name: 'A-1',
children: [
{name: "A-1-A"},
{name: "A-1-B"},
]
} object
it I call like this searchFn(tree,'A-1-A') it should return this
{name: "A-1-A"}
I tried like this but not working
function searchFn(obj ,searchText){
if(obj.name === searchText) return obj
if(obj.children.length > 0){
return searchFn(obj.children.pop(),searchText)
}
return null
}
You need to iterate the children of the object and take a variable for the result.
function searchFn(object, searchText) {
var result;
if (object.name === searchText) return object;
(object.children || []).some(o => result = searchFn(o, searchText));
return result || null;
}
let tree = { name: "A", children: [{ name: 'A-1', children: [{ name: "A-1-A" }, { name: "A-1-B" }] }, { name: 'B-1', children: [{ name: "B-1-A", children: [{ name: "B-11-A" }, { name: "B-11-B" }] }, { name: "B-1-B" }] }] };
console.log(searchFn(tree, 'foo'));
console.log(searchFn(tree, 'A-1'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
First, you should create a class for readability. Then you should have a loop inside the function that iterates over the children and only returns if a child is found.
class Node {
static fromStruct = (struct) => {
const tree = new Node(struct.name);
if(!struct.children) return tree;
for(const child of struct.children) {
tree.children.push(Node.fromStruct(child));
}
return tree;
}
constructor(name){
this.name = name;
this.children = [];
}
addChild = (parentName, childName) => {
const parent = this.searchFn(parentName);
if(!parent) throw new Error("Parent not found");
parent.children.push(new Node(childName));
}
searchFn = (name) => {
if(this.name === name) return this;
for(const child of this.children) {
const found = child.searchFn(name);
if(found !== null) return found;
}
return null;
}
}
const data = {
name: "A",
children: [
{
name: 'A-1',
children: [
{name: "A-1-A"},
{name: "A-1-B"},
]
},
{
name: 'B-1',
children: [
{
name: "B-1-A",
children: [
{name: "B-11-A"},
{name: "B-11-B"}
]
},
{name: "B-1-B"},
]
},
]
};
const tree = Node.fromStruct(data);
console.log(tree.searchFn("A-1-A"));
const tree = {"name":"A","children":[{"name":"A-1","children":[{"name":"A-1-A"},{"name":"A-1-B"}]},{"name":"B-1","children":[{"name":"B-1-A","children":[{"name":"B-11-A"},{"name":"B-11-B"}]},{"name":"B-1-B"}]}]}
const searchFn = (tree, name) => {
let result = null;
if (typeof tree !== "object") return result;
if (tree.name === name) return tree;
if (tree.children && tree.children.length) {
tree.children.some(data => (result = searchFn(data, name)));
}
return result;
};
console.log(searchFn(tree, "A-1-A"));
console.log(searchFn(tree, "A-1"));
console.log(searchFn(tree, ""));
.as-console-row {color: blue!important}
You can try something like this
let tree = { name: "A", children: [{ name: 'A-1', children: [{ name: "A-1-A" }, { name: "A-1-B" }] }, { name: 'B-1', children: [{ name: "B-1-A", children: [{ name: "B-11-A" }, { name: "B-11-B" }] }, { name: "B-1-B" }] }] };
function searchFn(obj, text){
if(obj.name === text) return obj
else{
if(obj && obj.children && Array.isArray(obj.children)){
for(let value of obj.children){
if(searchFn(value, text)){
return searchFn(value, text)
}
}
}
}
return null
}
console.log(searchFn(tree, 'A-1'))
console.log(searchFn(tree, 'A-1-A'))
console.log(searchFn(tree, 'A-A-A'))
The problem with your function is that it only checks for the popped element from the array in the children property of the passed object, and not the other elements. Try this:
function searchFn(obj ,searchText) {
if(obj.name === searchText) return obj;
if(obj.children) {
for (let x of obj.children) {
let y = searchFn(x,searchText);
if (y)
return y;
}
}
return null;
}
Related
I need to filter a nested structure, that looks like this, based on query. I need to return all objects, including the parent object of the subtree, which match the query string in object name. Please help, i am stuck.
[
{
name: 'bob',
type: 1,
children: [
{
name: 'bob',
type: 2,
children: [
{
name: 'mike',
type: 3,
children: [
{
name:'bob',
type: 7,
children: []
},
{
name: 'mike',
type: 9,
children: []
}
]
}
]
},
{
name: 'mike',
type: 2
}
]
}
]
Right now i am able to find a match in the tree recursively, but the function returns the object on the first match and does not search deeper on sub levels in the same object. Any suggestions, how i can modify the code to go search through all levels recursively?
return tree.map(copy).filter(function filterNested(node) {
if (node.name.toLowerCase().indexOf(query) !== -1) {
return true;
}
if (node.children) {
return (node.children = node.children.map(copy).filter(filterNested))
.length;
}
});
if I am searching for query 'bob', the expected result should be,
const arr = [
{
name: 'bob',
type: 1,
children: [
{
name: 'bob',
type: 2,
children: [
{
name: 'mike',
type: 3,
children: [
{
name:'bob',
type: 7
},
]
}
]
},
]
}
]
You could reduce the array and build new objects with optional children, if they have a length not zero.
function filter(array, fn) {
return array.reduce((r, o) => {
var children = filter(o.children || [], fn);
if (fn(o) || children.length) r.push(Object.assign({}, o, children.length && { children }));
return r;
}, []);
}
var data = [{ name: 'bob', type: 1, children: [{ name: 'bob', type: 2, children: [{ name: 'mike', type: 3, children: [{ name: 'bob', type: 7 }, { name: 'same', typ: 9 }] }] }, { name: 'mike', type: 2 }] }],
result = filter(data, ({ name }) => name.toLowerCase() === 'bob');
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Something like this, perhaps?
function nestedFilter(query, nodes) {
return nodes.reduce((result, node) => {
const filteredChildren = node.children && nestedFilter(query, node.children);
const nameMatches = node.name.toLowerCase().indexOf(query) !== -1;
const childrenMatch = filteredChildren && filteredChildren.length;
const shouldKeep = nameMatches || childrenMatch;
return shouldKeep
? result.concat({ ...node, children: filteredChildren })
: result;
}, []);
}
You can use a recursive reduce for this, where you check if there are any children or if the name matches before you accumulate the object:
const example = [{
name: 'bob',
type: 1,
children: [{
name: 'bob',
type: 2,
children: [{
name: 'mike',
type: 3,
children: [{
name: 'bob',
type: 7,
children: []
},
{
name: 'mike',
type: 9,
children: []
}
]
}]
},
{
name: 'mike',
type: 2
}
]
}];
function reduceName(accum, item, matcher) {
item.children = (item.children || []).reduce((a,i)=>reduceName(a,i,matcher),[]);
if (!item.children.length) delete item.children;
if (matcher(item) || item.children) accum.push(item);
return accum;
}
console.log(example.reduce((a,i)=>reduceName(a,i,x=>x.name==='bob'),[]));
I would just use good old visitor pattern to traverse and build new tree.
class Visitor {
constructor(predicate) {
this.predicate = predicate;
}
visit(item) {
if (Array.isArray(item)) {
return this.visitArray(item);
} else if (item) {
return this.visitItem(item);
}
}
visitArray(item) {
const result = [];
for (let e of item) {
const item = this.visit(e);
if (item) {
result.push(item);
}
}
return result;
}
visitItem(item) {
const children = this.visit(item.children);
const hasChildren = (children && children.length > 0);
if (hasChildren || this.predicate(item)) {
return {
name: item.name,
type: item.type,
children: children
}
}
return null;
}
}
const visitor = new Visitor((item) => item.name === "bob");
const result = visitor.visit(data);
console.log(result);
A wonderful time to learn about mutual recursion and continuation passing style
const data =
[{name:'bob',type:1,children:[{name:'bob',type:2,children:[{name:'mike',type:3,children:[ {name:'bob',type:7,children:[]},{name:'mike',type:9,children:[]}]}]},{name:'mike',type:2}]}]
const identity = x => x
const search = (all = [], query = identity, pass = identity) =>
all.flatMap(v => search1(v, query, pass))
const search1 = (one = {}, query = identity, pass = identity) =>
query(one)
? pass([ { ...one, children: search(one.children, query) } ])
: search
( one.children
, query
, children =>
children.length === 0
? pass([])
: pass([ { ...one, children } ])
)
const result =
search(data, x => x.name === "bob")
console.log(JSON.stringify(result, null, 2))
I do think that this question will always be relevant so here is how I did it !
After a few hours ;)
var res = yourArray.filter(function f(el) {
if (el.children.length > 0) {
el.children = el.childreb.filter(f);
}
if (el.name === "bob") return true; // you can put the condition you need here.
})
//res is your returned array
Trully hope this code will benefit some of youl :)
arr1 = [
{
name: "will",
value: "1"
},
{
name: "nelson",
value: "3"
}
];
and
arr2 = [
{
name: "will",
value: 1,
submenu: [
{
name: "ralph",
value: 2
}
]
}
]
then remove ralph from the second array. i already created a function who do it but just check the first element and not verify the submenu.
comparador(arrSecundario) {
return (arrAtual) => {
return arrSecundario.filter(function (other) {
return other.value === arrAtual.value;
}).length !== 0;
};
}
this.arr2.filter(this.comparador(this.arr1));
Need to map array after filter. You can do like this and can also add "prop === 'submenu'" for specifically checking submenu inner array only.
var arr1 = [
{
name: "will",
value: "1"
},
{
name: "nelson",
value: "3"
}
];
var arr2 = [
{
name: "will",
value: 1,
submenu: [
{
name: "ralph",
value: 2
},
// {
// name: "nelson",
// value: 3
// }
]
}
];
function filterComparador(arrSecundario) {
return (arrAtual) => {
return arrSecundario.filter((other) => {
return other.value == arrAtual.value;
}).length != 0;
};
}
function mapComparador(arrSecundario) {
return (arrAtual) => {
Object.keys(arrAtual).forEach((prop) => {
if (Array.isArray(arrAtual[prop])) {
let propValue = arrAtual[prop].filter(this.filterComparador(arrSecundario));
if (propValue.length > 0) {
arrAtual[prop] = propValue.map(this.mapComparador(this.arrSecundario));
} else {
delete arrAtual[prop];
}
}
});
return arrAtual;
};
}
var manipulatedArray = this.arr2.filter(this.filterComparador(this.arr1))
.map(this.mapComparador(this.arr1));
console.log(manipulatedArray);
I want to retrieve all child ids of a specific group, which can be deeply nested or not.
Here is a sample json:
[
{
id: 1,
name: 'Desjardins Group 1',
children: [
{ id: 2, name: 'Analysts', children: [] },
{ id: 3, name: 'Administration', children: [] }
]
},
{
id: 4,
name: 'Desjardins Group 2',
children: [
{ id: 5, name: 'Consultants1', children: [] },
{
id: 6,
name: 'Consultant2',
children: [
{
id: 7, name: 'Interns', children: [
{ id: 8, name: 'subInterns1', children: [] },
{ id: 9, name: 'subInterns2', children: [] },
{ id: 10, name: 'subInterns3', children: [] }
]
}
]
}
]
}
]
I'm trying to make a function that takes an id has a parameter, and return all child ids.
Ex: getChildGroups(6) would return 7, 8, 9 and 10.
I guess recursive function and filters are the way to go, but i can't find a proper example.
Here's a simplified version of Johann Bauer's answer.
The first function just finds the first node that matches the given ID, with no need for any accumulation of data:
function findNode(data, id) {
if (!Array.isArray(data)) return;
for (let entry of data) {
if (entry.id === id) {
return entry;
} else {
const node = findNode(entry.children, id);
if (node) {
return node;
}
}
}
}
This second function just gets the child IDs, storing them in the passed array, without any intermediate arrays being created:
function getChildIds(node, result = []) {
if (!node) return;
if (!Array.isArray(node.children)) return;
for (let entry of node.children) {
result.push(entry.id);
getChildIds(entry, result);
}
return result;
}
It might be a good idea to split your problem into two smaller problems:
Find a group of ID x somewhere nested in the graph
Given a node, return all their sub-node IDs recursively
The solution to the first problem could look something like this:
function findGroupId(o, id) {
if (o.id == id) {
// We found it!
return o;
}
if (Array.isArray(o)) {
// If we start with a list of objects, pretend it is the root node
o = {children: o}
}
let results = [];
for (let c of o.children) {
// recursively call this function again
results.push(findGroupId(c, id))
}
// return the first matching node
return results.filter(r => r !== undefined)[0];
}
And for the second problem:
function getAllChildrenIDs(o) {
if (o.children === undefined)
return [];
let ids = [];
for (c of o.children) {
ids.push(c.id);
// recursively call this function again
for (id of getAllChildrenIDs(c))
ids.push(id);
}
return ids;
}
And if we put this together:
let example = [{
id: 1,
name: 'Desjardins Group 1',
children: [{
id: 2,
name: 'Analysts',
children: []
},
{
id: 3,
name: 'Administration',
children: []
}
]
},
{
id: 4,
name: 'Desjardins Group 2',
children: [{
id: 5,
name: 'Consultants1',
children: []
},
{
id: 6,
name: 'Consultant2',
children: [{
id: 7,
name: 'Interns',
children: [{
id: 8,
name: 'subInterns1',
children: []
},
{
id: 9,
name: 'subInterns2',
children: []
},
{
id: 10,
name: 'subInterns3',
children: []
}
]
}]
}
]
}
];
function findGroupId(o, id) {
if (o.id == id) {
return o;
}
if (Array.isArray(o)) {
o = {
children: o
}
}
let results = [];
for (let c of o.children) {
results.push(findGroupId(c, id))
}
return results.filter(r => r !== undefined)[0];
}
function getAllChildrenIDs(o) {
if (o.children === undefined)
return [];
let ids = [];
for (c of o.children) {
ids.push(c.id);
for (id of getAllChildrenIDs(c))
ids.push(id);
}
return ids;
}
console.log(getAllChildrenIDs(findGroupId(example, 6)))
I am trying to create an object, and within this object will be the name and an array of other objects under children. Really I want to create a hierarchy from another object.
I have tried to create a recursive function but what I end up with is a vertical slice rather than the whole picture. I am unsure how to adjust my recursion to go back add iterate through the other horizontal objects.
buildHierarchy(json) {
console.log("Entered Build Hierarchy");
let newObject;
newObject = this.buildChildren(json);
console.log(newObject);
return newObject
}
buildChildren(json) {
let returnObject;
for (var key in json) {
returnObject = {
name: key,
children: []
};
var subObject = json[key];
if (Array.isArray(subObject)) {
returnObject = {
name: key,
_proficiency: subObject
}
} else {
returnObject["children"].push(this.buildChildren(subObject))
}
}
return returnObject;
}
Imagine you have this json file below
{users:
{sandy: {
posts: [
{ title: 'Bar', comments: [ 'Ok' ] },
]
followers: [
{ name: 'Foo' },
]
}
ron: {
photos: [
{ title: 'Foo', comments: [ 'Ok' ] },
]
}
}
}
I am looking for something like this...
{
name: "users",
children: [
{
name: "sandy",
children: [
{
name: "posts",
children: [
{
name: "Bar",
comments: "OK"
}],
{ name: "followers"
children: [
{
name: "Foo"
}
]
}
}
]
},
{
name: "ron",
photos: [
{
name: "photos",
children: [
{
name: "Foo",
comments: "OK"
}
]
}
]
}
]
}
From what I see as the output I'm making these inferences:
If the child object is an array, it is blindly copied to children key
If the child element is an object, then they are changed to {name: <KEY>, children: <VALUE>}
function buildChildren(json) {
let returnObject = [];
if (typeof json !== 'object') {
return json;
}
for (var key in json) {
if (Array.isArray(json)) {
returnObject.push(buildChildren(json[key]));
} else {
returnObject.push({
name: key,
children: buildChildren(json[key])
});
}
}
return returnObject;
}
function buildHierarchy(json) {
console.log("Entered Build Hierarchy");
let newObject;
newObject = buildChildren(json);
console.log(newObject);
}
function buildChildren(json) {
if (Array.isArray(json)) {
return {
_proficiency: json
}
}
var children = Object.keys(json);
let final = [];
for (var i = 0; count = children.length, i < count; i++) {
let result = {
name: children[i]
}
let d = buildChildren(json[children[i]]);
if (d._proficiency) {
result._proficiency = d._proficiency;
} else {
result.children = d;
}
final.push(result);
}
return final;
}
let say i have a tree in javascript
a1
--b
----c1
a2
--b2
--b3
----c2
and if i wanted to find c2, it should return a2->b3->c2
Lets say my json looked like this?
treeFamily = {
name : "Parent",
children: [{
name : "Child1",
children: [{
name : "Grandchild1",
children: []
},{
name : "Grandchild2",
children: []
},{
name : "Grandchild3",
children: []
}]
}, {
name: "Child2",
children: []
}]
};
You could check if the nested children have the wanted key/value. Then take the name and hand over the result to the outer call.
function findPath(array, target) {
var path;
array.some(({ name, children }) => {
var temp;
if (name === target) {
path = [name];
return true;
}
if (temp = findPath(children, target)) {
path = [name, ...temp];
return true;
}
});
return path;
}
var treeFamily = { name: "Parent", children: [{ name: "Child1", children: [{ name: "Grandchild1", children: [] }, { name: "Grandchild2", children: [] }, { name: "Grandchild3", children: [] }] }, { name: "Child2", children: [] }] };
console.log(findPath([treeFamily], 'Grandchild2'));
console.log(findPath([treeFamily], 'foo'));
You can use for...of to search the children by calling the function recursively. If the target is found, the name is returned, and combined with the previous names. If not, the function will return undefined. Alternatively, you can return an empty array.
const findPath = (targetName, { name, children }) => {
if(name === targetName) return [name];
for(const child of children) {
const result = findPath(targetName, child);
if(result) return [name, ...result];
}
// if child not found implicitly return undefined or return [] to get an empty array
};
const treeFamily = { name: "Parent", children: [{ name: "Child1", children: [{ name: "Grandchild1", children: [] }, { name: "Grandchild2", children: [] }, { name: "Grandchild3", children: [] }] }, { name: "Child2", children: [] }] };
console.log(findPath('Child2', treeFamily));
console.log(findPath('Grandchild3', treeFamily));
console.log(findPath('Grandchild400', treeFamily));