I have a list which could look like this:
let list = [
{
name: "Level 1",
children: [
{
name: "Level 2",
children: [
{
name: "Level 3A",
children: [],
},
{
name: "Level 3B",
children: [],
},
{
name: "Level 3C",
children: [],
},
],
},
],
},
];
As you can see it consists of elements with 2 properties: name and children.
This is simple list but it can be nested many levels down and each element could have a lot of children.
Moreover i have sample array of arrays of strings like this:
const filterRows = [
["Level 1", "Level 2", "Level 3A"],
["level 1", "Level 2", "Level 3C"]
]
Each of this 2 arrays of strings represents each 'row' in my list. (Here we don't have ["level 1", "Level 2", "Level 3B"])
My goal is to make something like filter. So based on filterRows i want my list to be:
let filteredList = [
{
name: "Level 1",
children: [
{
name: "Level 2",
children: [
{
name: "Level 3A",
children: [],
},
{
name: "Level 3C",
children: [],
},
],
},
],
},
];
As you can see in filteredList we only have rows specified in filterRows (so here we don't have "Level 3B")
Could you help me create function to do that?
As i said list (initial list) could be much more complicated I simplify it. Also filterRows can be different (but always correctly represent some rows in our list). So it cannot be hardcoded.
With matching strings, you could build a tree and rebuild the structure with the wanted items.
const
filter = level => (r, { name, children }) => {
const next = level[name];
if (next) {
if (Object.keys(next).length) children = children.reduce(filter(next), []);
r.push({ name, children });
}
return r;
},
list = [{ name: "Level 1", children: [{ name: "Level 2", children: [{ name: "Level 3A", children: [] }, { name: "Level 3B", children: [] }, { name: "Level 3C", children: [] }] }] }],
filterRows = [["Level 1", "Level 2", "Level 3A"], ["Level 1", "Level 2", "Level 3C"]],
tree = filterRows.reduce((r, a) => {
a.reduce((o, k) => o[k] ??= {}, r);
return r;
}, {}),
result = list.reduce(filter(tree), []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Related
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 1 year ago.
Improve this question
I have a list which could look like this:
let list = [
{
name: "Level 1",
children: [
{
name: "Level 2",
children: [
{
name: "Level 3A",
children: [
{
name: "Level 4A",
children: []
}
],
},
{
name: "Level 3B",
children: [],
},
{
name: "Level 3C",
children: [],
},
],
},
],
},
];
As you can see it consists of elements with 2 properties: name and children. This is simple list but it can be nested many levels down and each element could have a lot of children.
Based on that list i want to create array of arrays of strings e.g.
let levels = [
["Level 1"],
["Level 1", "Level 2"],
["Level 1", "Level 2", "Level 3A"],
["Level 1", "Level 2", "Level 3A", "Level 4A"],
["Level 1", "Level 2", "Level 3B"],
["Level 1", "Level 2", "Level 3C"],
]
So as you can see each 'row' of my list has a representation in my levels array.
Could you help me write achieve that?
You can define a function to traverse the tree structure whilst accumulating the path along the way.
function getLevels(list) {
const levels = [];
const searchForLevels = ({ name, children }, path) => {
levels.push([...path, name]);
path.push(name);
children.forEach(child => searchForLevels(child, path));
path.pop();
};
list.forEach(child => searchForLevels(child, []));
return levels;
}
let list = [
{
name: "Level 1",
children: [
{
name: "Level 2",
children: [
{
name: "Level 3A",
children: [
{
name: "Level 4A",
children: []
}
],
},
{
name: "Level 3B",
children: [],
},
{
name: "Level 3C",
children: [],
},
],
},
],
},
];
console.log(getLevels(list));
This can be done by traversing the tree and printing the path taken at each step
let list = [
{
name: "Level 1",
children: [
{
name: "Level 2",
children: [
{
name: "Level 3A",
children: [
{
name: "Level 4A",
children: []
}
],
},
{
name: "Level 3B",
children: [],
},
{
name: "Level 3C",
children: [],
},
],
},
],
},
];
let root = list[0]
function Traverse (root, path){
console.log(path + " " + root.name)
root.children.forEach(child => Traverse (child, path + " " + root.name));
}
Traverse(root, "");
You could do it by writing a recursive function.
Though on SO, usually, people would suggest you try to solve it yourself first. But I myself find that writing recursive functions is not an easy task (in terms of understanding how it works). So I'm happy to give you a hand.
const recursion = ({ name, children }, accumulator = []) => {
if (name) accumulator.push(name);
res.push(accumulator);
children.forEach((element) => recursion(element, [...accumulator]));
// have to store accumulator in new reference,
// so it would avoid override accumulators of other recursive calls
};
let list = [
{
name: "Level 1",
children: [
{
name: "Level 2",
children: [
{
name: "Level 3A",
children: [
{
name: "Level 4A",
children: [],
},
],
},
{
name: "Level 3B",
children: [],
},
{
name: "Level 3C",
children: [],
},
],
},
],
},
];
const res = [];
const recursion = ({ name, children }, accumulator = []) => {
if (name) accumulator.push(name);
res.push(accumulator);
children.forEach((element) => recursion(element, [...accumulator]));
};
list.forEach((element) => recursion(element));
console.log(res);
You could take flatMap with a recursive callback.
const
getPathes = ({ name, children }) => children.length
? children.flatMap(getPathes).map(a => [name, ...a])
: [[name]],
list = [{ name: "Level 1", children: [{ name: "Level 2", children: [{ name: "Level 3A", children: [{ name: "Level 4A", children: [] }] }, { name: "Level 3B", children: [] }, { name: "Level 3C", children: [] }] }] }],
pathes = list.flatMap(getPathes);
console.log(pathes);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I have this array of deeply nested objects
[
{
id: 1,
name: "task 1",
wbs: 1
children: [
{
id: 2,
name: "task 1.1",
wbs: 1.1
children: []
}
]
},
{
id: 1,
name: "task 2",
wbs: 2
children: [
{
id: 2,
name: "task 2.1",
wbs: 2.1
children: [
{
id: 2,
name: "task 2.1.1",
wbs: 2.1.1
children: []
}
]
}
]
},
{
id: 1,
name: "task 3",
wbs: 3
children: []
},
]
The wbs number should be generated according to the position and depth the object is at. How can I generate this number when I push a new object to the array.
That is, if I push a new object to the children array of task 1, the wbs number should be 1.2. How can I achieve this?
Because arrays keep a reliable insertion order, we can do this rather easily actually! We can even create a simple prototype function to streamline this process if you're doing this often in your project:
let obj = [
{ id: 1, name: "task 1", wbs: "1", children: [ { id: 2, name: "task 1.1", wbs: "1.1", children: [] } ] },
{ id: 1, name: "task 2", wbs: "2", children: [ { id: 2, name: "task 2.1", wbs: "2.1", children: [ { id: 2, name: "task 2.1.1", wbs: "2.1.1", children: [] } ] } ] },
{ id: 1, name: "task 3", wbs: "3", children: [] }
];
Array.prototype.wbsPush = function() {
const array = this;
if (!array.every(el => el.wbs)) return false;
const previousWbs = array[array.length - 1].wbs;
const newWbs = previousWbs.split('.').reverse().map((e,i) => i ? e : parseInt(e) + 1).reverse().join('.');
array.push({ id: array[array.length - 1].id + 1, name: `task ${newWbs}`, wbs: newWbs, children: [] });
}
obj.wbsPush(); // -> wbs: 4
obj[0].children.wbsPush(); // -> wbs: 1.2
console.log(obj);
I have an array:
const arr = [
{
name: "name 1",
dontShow: true,
children: [
{
name: "name 2",
key4: 4,
dontShow: false,
children: [],
},
],
},
{
name: "name 3",
dontShow: false,
children: [
{
name: "name 4",
dontShow: true,
children: [
{
name: "name 5",
dontShow: false,
children: null,
},
],
},
],
},
];
I need an array of names from every object, except those that have property dontShow: true
So from that example I would expect such array:
["name2", "name3", "name5"]
Basically, I need to get a flat array from tree-like structure, lodash/underscore solutions would be also great, I just didn't find them
You can use a recursive function
const arr = [{ name: "name 1", dontShow: true, children: [{ name:"name 2", key4: 4, dontShow: false, children: [], }, ],},{name: "name 3",dontShow: false,children: [{ name: "name 4", dontShow: true, children: [{ name: "name 5", dontShow: false, children: null,},],}, ],},];
let final = (arr, result = []) => {
if (Array.isArray(arr)) {
arr.forEach(obj => {
if (!obj.dontShow) {
result.push(obj.name)
}
if (Array.isArray(obj.children)) {
final(obj.children, result)
}
})
}
return result
}
console.log(final(arr))
You could get a flat array of names with a look to dontShow.
const
getNames = array => array.flatMap(({ name, dontShow, children }) => [
...(dontShow ? [] : [name]),
...getNames(children || [])
]),
array = [{ name: "name 1", dontShow: true, children: [{ name: "name 2", key4: 4, dontShow: false, children: [] }] }, { name: "name 3", dontShow: false, children: [{ name: "name 4", dontShow: true, children: [{ name: "name 5", dontShow: false, children: null, }] }] }],
result = getNames(array);
console.log(result);
I have recursive array of object collection which need to convert to object,
I tried something like below but I want dynamically.
Can anyone give suggestions or share your ideas it will helpful for me
let arr = list;
const jsonObj = {};
const arrayToObject = (array, keyField) =>
array.reduce((obj, item) => {
obj[item[keyField]] = item
return obj
}, {})
arr.forEach((item) => {
jsonObj[item.key] = {};
if(item.children.length > 0) {
jsonObj[item.key] = arrayToObject(item.children, 'key');
}
})
INPUT
list = [
{
key: "Parent 1",
value: "",
children: [
{ key: 11, value: "Child 1", children: [] },
{ key: 12, value: "Child 2", children: [] }
]
},
{
key: "Parent 2",
value: "",
children: [
{
key: 20,
value: "",
children: [
{ key: 21, value: "Grand Child 1", children: [] },
{ key: 22, value: "Grand Child 2", children: [] }
]
}
]
},
{
key: "Parent 3",
value: "",
children: [
{ key: 31, value: "Child 1", children: [] },
{ key: 32, value: "Child 2", children: [] }
]
},
];
OUTPUT
{
"Parent 1": {
"11": "Child 1",
"12": "Child 2",
},
"Parent 2": {
"20": {
"21": "Grand Child 1",
"22": "Grand Child 2",
}
},
"Parent 3": {
"31": "Child 1",
"32": "Child 2",
}
}
You could use reduce to recursively loop the array. If the current object has a non-zero children array, call transform recursively. Else, use the value for key in the accumulator
const list = [{key:"Parent 1",value:"",children:[{key:11,value:"Child 1",children:[]},{key:12,value:"Child 2",children:[]}]},{key:"Parent 2",value:"",children:[{key:20,value:"",children:[{key:21,value:"Grand Child 1",children:[]},{key:22,value:"Grand Child 2",children:[]}]}]},{key:"Parent 3",value:"",children:[{key:31,value:"Child 1",children:[]},{key:32,value:"Child 2",children:[]}]}];
function transform(array) {
return array.reduce((r, { key, value, children }) => {
if (children.length)
r[key] = transform(children)
else
r[key] = value;
return r;
}, {})
}
console.log(transform(list))
One-liner using arrow function and Object.assgin():
const transform = (array) => array.reduce((r, { key, value, children }) =>
Object.assign(r, { [key]: children.length ? transform(children): value }), {})
I am having a tree structured JSON which should be filtered and the result should retain the tree structure.
var tree = [
{
text: "Parent 1",
nodes: [
{
text: "Child 1",
type: "Child",
nodes: [
{
text: "Grandchild 1"
type: "Grandchild"
},
{
text: "Grandchild 2"
type: "Grandchild"
}
]
},
{
text: "Child 2",
type: "Child"
}
]
},
{
text: "Parent 2",
type: "Parent"
},
{
text: "Parent 3",
type: "Parent"
}
];
Example :
1)If search query is Parent 1
Expected result :
[
{
text: "Parent 1",
nodes: [
{
text: "Child 1",
type: "Child",
nodes: [
{
text: "Grandchild 1"
type: "Grandchild"
},
{
text: "Grandchild 2"
type: "Grandchild"
}
]
},
{
text: "Child 2",
type: "Child"
}
]
}
]
2)If search query is Child 1
Expected result :
[
{
text: "Parent 1",
nodes: [
{
text: "Child 1",
type: "Child",
nodes: [
{
text: "Grandchild 1"
type: "Grandchild"
},
{
text: "Grandchild 2"
type: "Grandchild"
}
]
}
]
}
]
3)If search query is Grandchild 2
Expected result :
[
{
text: "Parent 1",
nodes: [
{
text: "Child 1",
type: "Child",
nodes: [
{
text: "Grandchild 2"
type: "Grandchild"
}
]
}
]
}
]
I need to retain the tree structure based on the node level (type here). So far I have tried filtering recursively but couldn't re-map the results.
angular.module("myApp",[])
.filter("filterTree",function(){
return function(items,id){
var filtered = [];
var recursiveFilter = function(items,id){
angular.forEach(items,function(item){
if(item.text.toLowerCase().indexOf(id)!=-1){
filtered.push(item);
}
if(angular.isArray(item.items) && item.items.length > 0){
recursiveFilter(item.items,id);
}
});
};
recursiveFilter(items,id);
return filtered;
};
});
});
My JSON is pretty large and hence remapping based on types are expected to be done in the filter itself.
Please advice.
You could use a nested recursive approach and filter the tree, while respecting the found item.
This solution does not mutate the original data.
function filter(array, text) {
const getNodes = (result, object) => {
if (object.text === text) {
result.push(object);
return result;
}
if (Array.isArray(object.nodes)) {
const nodes = object.nodes.reduce(getNodes, []);
if (nodes.length) result.push({ ...object, nodes });
}
return result;
};
return array.reduce(getNodes, []);
}
var tree = [{ text: "Parent 1", nodes: [{ text: "Child 1", type: "Child", nodes: [{ text: "Grandchild 1", type: "Grandchild" }, { text: "Grandchild 2", type: "Grandchild" }] }, { text: "Child 2", type: "Child" }] }, { text: "Parent 2", type: "Parent" }, { text: "Parent 3", type: "Parent" }];
console.log(filter(tree, 'Parent 1'));
console.log(filter(tree, 'Child 1'));
console.log(filter(tree, 'Grandchild 2'));
.as-console-wrapper { max-height: 100% !important; top: 0; }