So I have the following Array. I can push to the parent level and child level however I can't seem to push to Children of Children or deeper. Here's the Array:
TREE_DATA: SegmentCategory[] = [
{
id: 1,
name: 'Category 1',
children: [
{
id: 2,
name: 'Category 1-1',
children: [
{
id: 4,
name: 'Category 1-1-a',
children: []
},
{
id: 5,
name: 'Category 1-1-b',
children: []
},
]
},
{
id: 6,
name: 'Category 1-2',
children: [
{
id: 7,
name: 'Category 1-2-a',
children: [
{
id: 8,
name: 'Category 1-2-1',
children: [
{
id: 9,
name: 'Category 1-2-2-a',
children: []
}
]
}
]
}
]
}
]
},
{
id: 10,
name: 'Category 2',
children: []
}
];
And here is the method I am using to add data to the array:
createCategory() {
this.id++;
if (this.formGroup.controls['parentCategory'].value == null) {
this.TREE_DATA.push(
{
id: this.id,
name: this.formGroup.controls['categoryName'].value,
children: []
});
} else {
this.TREE_DATA.forEach((v, index) => {
if (v.id === this.formGroup.controls['parentCategory'].value.id) {
this.TREE_DATA[index].children.push(
{
id: this.id,
name: this.formGroup.controls['categoryName'].value,
children: []
}
);
}
});
}
}
I know that I can just add an additional foreach however this doesn't seem like the best way to handle this, as I could want to go 5-6 layers down at any time to add a new nested child. What would be the best way to handle this?
You could take a recursive function for finding an object in the tree.
function find(array, id) {
var result;
array.some(object => {
if (object.id === id) return result = object;
return result = find(object.children || [], id);
});
return result;
}
var data = [{ id: 1, name: 'Category 1', children: [{ id: 2, name: 'Category 1-1', children: [{ id: 4, name: 'Category 1-1-a', children: [] }, { id: 5, name: 'Category 1-1-b', children: [] }] }, { id: 6, name: 'Category 1-2', children: [{ id: 7, name: 'Category 1-2-a', children: [{ id: 8, name: 'Category 1-2-1', children: [{ id: 9, name: 'Category 1-2-2-a', children: [] }] }] }] }] }, { id: 10, name: 'Category 2', children: [] }];
console.log(find(data, 9));
console.log(find(data, 'unknown'));
Related
First I must say sorry if this question is already answered, but I have not found the answer I am looking for :(
I have an array of objects with unknown nesting depth (it can be 20-30 or even more) and I want to filter it's 'name' property based on input field value.
public nestedArray = [
{id: 1, name: 'Example_1', children: []},
{id: 2, name: 'Test', children: []},
{id: 3, name: 'Test Name', children: [
{id: 10, name: 'Child name', children: [
{id: 20, name: 'Example_14', children: []},
{id: 30, name: 'Last Child', children: []}
]
}
]
}
];
The result I want to receive is an array of objects with only one level deep with 'name' field which includes input value.
For example my input value is 'am', so the result would be:
resultsArray = [
{id: 1, name: 'Example_1'},
{id: 3, name: 'Test Name'},
{id: 10, name: 'Child name'},
{id: 20, name: 'Example_14'}
];
There is no problem to do it on the first level like that:
public filter(array: any[], input_value: string): void {
array = array.filter(el => {
return el.name.toLowerCase().includes(input_value.toLowerCase()));
}
}
Thanks in advance!
You could map the array and their children and take a flat result of objects where the string is matching the name property.
const
find = value => ({ children, ...o }) => [
...(o.name.includes(value) ? [o] : []),
...children.flatMap(find(value))
],
data = [{ id: 1, name: 'Example_1', children: [] }, { id: 2, name: 'Test', children: [] }, { id: 3, name: 'Test Name', children: [{ id: 10, name: 'Child name', children: [{ id: 20, name: 'Example_14', children: [] }, { id: 30, name: 'Last Child', children: [] }] }] }],
result = data.flatMap(find('am'));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Another solution with a single result array and a classic approach.
const
find = (array, value) => {
const
iter = array => {
for (const { children, ...o } of array) {
if (o.name.includes(value)) result.push(o);
iter(children);
}
},
result = [];
iter(array);
return result;
},
data = [{ id: 1, name: 'Example_1', children: [] }, { id: 2, name: 'Test', children: [] }, { id: 3, name: 'Test Name', children: [{ id: 10, name: 'Child name', children: [{ id: 20, name: 'Example_14', children: [] }, { id: 30, name: 'Last Child', children: [] }] }] }],
result = find(data, 'am');
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
My nested json array looks like:
[
{
id: 1,
name: "Mike",
children: [
{ id: 2, name: "MikeC1" },
{ id: 3, name: "MikeC2" },
{
id: 4, name: "MikeC3",
children: [{ id: 5, name: "MikeCC1" }]
},
]
},
{
id: 6,
name: "Json",
children: [
{ id: 7, name: "JsonC1" },
{ id: 8, name: "JsonC2" },
{
id: 9, name: "JsonC3",
children: [{ id: 10, name: "JsonCC1" },{ id: 11, name: "JsonCC2" }]
},
]
}
]
Now I get a id like "11"
then get the parent ids array in json like [6,9,11]
How to do?
var id = 11
console.log(findParent(id))
//result is [6,9,11]
You need to do recursive search
const persons = [
{
id: 1,
name: "Mike",
children: [
{ id: 2, name: "MikeC1" },
{ id: 3, name: "MikeC2" },
{
id: 4, name: "MikeC3",
children: [{ id: 5, name: "MikeCC1" }]
},
]
},
{
id: 6,
name: "Json",
children: [
{ id: 7, name: "JsonC1" },
{ id: 8, name: "JsonC2" },
{
id: 9, name: "JsonC3",
children: [{ id: 10, name: "JsonCC1" },{ id: 11, name: "JsonCC2" }]
},
]
}
];
function searchRecursive(items, id) {
const allIds = [];
items.forEach(item => {
if(item.id === id) {
allIds.push(item.id);
}
else if(item.children) {
const ids = searchRecursive(item.children, id);
if(ids.length) allIds.push(item.id);
ids.forEach(id => allIds.push(id));
}
});
return allIds;
}
console.log(searchRecursive(persons, 11));
I want to keep the parent-child relationship of the tree node.
I have a JSON tree, like this
{
id: null,
children: [
{
id: 1,
children: [
{
id: 11,
children: [
{
id: 111
children: []
}
]
},
{
id: '12',
children: []
},
]
},
{
id: '2',
children: [
{
id: '21',
children: []
},
{
id: '22',
children: [
{
id: '221',
children: []
}
]
},
]
},
]
}
I want flat the tree, like this
[
{ id: 1, parent: null,},
{ id: 11, parent: 1, },
{ id: 111, parent: 11, },
{ id: 2, parent: null, },
{ id: 21, parent: 2, },
...
]
parent automatic generated
Is there any good way?
You could use flatMap method and create recursive function that will return 1D array as a result.
const data = {"id":null,"children":[{"id":1,"children":[{"id":11,"children":[{"id":111,"children":[]}]},{"id":"12","children":[]}]},{"id":"2","children":[{"id":"21","children":[]},{"id":"22","children":[{"id":"221","children":[]}]}]}]}
const flatten = (data, parent = null) =>
data.flatMap(({ id, children }) => ([
{ id, parent },
...flatten(children, id)
]))
const result = flatten(data.children);
console.log(result)
You could get the object and return an array of the flat children.
const
getFlat = ({ id, children = [] }) =>
children.flatMap(o => [{ id: o.id, parent: id }, ...getFlat(o)]);
var data = { id: null, children: [{ id: 1, children: [{ id: 11, children: [{ id: 111, children: [] }] }, { id: '12', children: [] }] }, { id: '2', children: [{ id: '21', children: [] }, { id: '22', children: [{ id: '221', children: [] }] }] }] },
flat = getFlat(data);
console.log(flat);
.as-console-wrapper { max-height: 100% !important; top: 0; }
If compatbility with Internet Explorer is important, then the following approach which avoids the need for Array#flatMap might suit:
const input={id:null,children:[{id:'1',children:[{id:'11',children:[{id:'111',children:[]}]},{id:'12',children:[]}]},{id:'2',children:[{id:'21',children:[]},{id:'22',children:[{id:'221',children:[]}]}]}]};
const result = [];
// Define recursive function to walk through the data tree
// and produce a flat array of required data
const recurse = (item, parent) => {
// Only add item to result if it has valid id
if (item.id) {
result.push({
id: item.id,
parent: parent ? parent.id : null
});
}
// Iterate the children of this item, traversing and
// processing them in the same way
item.children.forEach(child => recurse(child, item))
}
recurse(input);
console.log(result);
You can use a simple recursion to achieve this
let data = {
id: 1,
name: 'a1',
children: [{
id: 2,
name: 'a2',
children: [{
id: 3,
name: 'b1',
children: []
},
{
id: 4,
name: 'b2',
children: []
}
]
}]
}
function flatten(data){
output = [];
return (function indentHandler(data, level, parent){
output.push({id: data['id'], parent: parent})
if (data['children'].length === 0){
return output;
}
level += 1;
data['children'].forEach(function(child){
return indentHandler(child, level, data.name);
});
return output;
})(data, 0, null);
}
flatten(data);
use recursion
var tree = {
id: null,
children: [{
id: 1,
children: [{
id: 11,
children: [{
id: 111,
children: []
}]
},
{
id: '12',
children: []
},
]
},
{
id: '2',
children: [{
id: '21',
children: []
},
{
id: '22',
children: [{
id: '221',
children: []
}]
},
]
}
]
};
var flat = [];
function flatArray(arr, parentId) {
arr.forEach(function(el) {
flat.push({
id: el.id,
parent: parentId || null
});
if (el.children)
flatArray(el.children, el.id);
});
}
flatArray(tree.children, 0)
console.log(flat);
I have two separate arrays of objects that I need to merge based if a specific key value matches. Might make more sense after analyzing the data:
Array 1
let categories = [
{ id: 5, slug: 'category-5', items: [] },
{ id: 4, slug: 'category-4', items: [] },
{ id: 3, slug: 'category-3', items: [] },
]
Array 2
let items = [
{ id: 5, data: [{ title: 'item title', description: 'item description' }] },
{ id: 5, data: [{ title: 'item title 2', description: 'item description 2' }] },
{ id: 4, data: [{ title: 'item title 4', description: 'item description 4' }] },
]
Expected Output
let mergedOutput = [
{ id: 5, slug: 'category-5',
items: [
{ title: 'item title', description: 'item description' },
{ title: 'item title 2', description: 'item description 2' }
]
},
{ id: 4, slug: 'category-4',
items: [
{ title: 'item title 4', description: 'item description 4' },
]
},
{ id: 3, slug: 'category-3', items: [] },
]
So....I need to add Array 2 to Array 1 if their id's match.
Array 1 will stay the same, but if Array 2 matches, the items property of Array 1 (empty) will be replaced by the data property of Array 2
I know this is a pretty basic / and redundant question, but I can't find the resources for my use case / object structure.
I was able to easily group arrays with lodash -- so if there is a similar solution with that library -- that would good! Or just some direction would suffice.
Thanks in advance!
You can loop first array and then use filter to get objects with same id as current element and add that items to current object.
let categories = [
{ id: 5, slug: 'category-5', items: [] },
{ id: 4, slug: 'category-4', items: [] },
{ id: 3, slug: 'category-3', items: [] },
]
let items = [
{ id: 5, data: [{ title: 'item title', description: 'item description' }] },
{ id: 5, data: [{ title: 'item title 2', description: 'item description 2' }] },
{ id: 4, data: [{ title: 'item title 4', description: 'item description 4' }] },
]
categories.forEach(function(e) {
var i = items.filter(a => a.id == e.id).map(a => a.data);
e.items = i;
})
console.log(categories)
You could reduce the items into categories:
let res = items.reduce((a, b) => {
let it = a.find(e => e.id === b.id);
if (! it) return a;
it.items = it.items.concat(b.data);
return a;
}, categories);
let categories = [{
id: 5,
slug: 'category-5',
items: []
},
{
id: 4,
slug: 'category-4',
items: []
},
{
id: 3,
slug: 'category-3',
items: []
},
];
let items = [{
id: 5,
data: [{
title: 'item title',
description: 'item description'
}]
},
{
id: 5,
data: [{
title: 'item title 2',
description: 'item description 2'
}]
},
{
id: 4,
data: [{
title: 'item title 4',
description: 'item description 4'
}]
},
];
let res = items.reduce((a, b) => {
let it = a.find(e => e.id === b.id);
if (! it) return a;
it.items = it.items.concat(b.data);
return a;
}, categories);
console.log(res);
It might be faster to get the ids in an object first, so we don't have to use find on the same id many times:
function merge(result, toMerge, mergeInto) {
let i = 0, hm = {};
for (let {id} of categories) {
hm[id] = i;
i++;
}
return toMerge.reduce((a,b) => {
let it = a[hm[b.id]];
if (!it) return a;
it[mergeInto] = it[mergeInto].concat(b.data);
return a;
}, result);
}
let categories = [
{ id: 5, slug: 'category-5', items: [] },
{ id: 4, slug: 'category-4', items: [] },
{ id: 3, slug: 'category-3', items: [] },
];
let items = [
{ id: 5, data: [{ title: 'item title', description: 'item description' }] },
{ id: 5, data: [{ title: 'item title 2', description: 'item description 2' }] },
{ id: 4, data: [{ title: 'item title 4', description: 'item description 4' }] },
];
function merge(result, toMerge, mergeInto) {
let i = 0, hm = {};
for (let {id} of categories) {
hm[id] = i;
i++;
}
return toMerge.reduce((a,b) => {
let it = result[hm[b.id]];
if (!it) return a;
it[mergeInto] = it[mergeInto].concat(b.data);
return a;
}, result);
}
console.log(merge(categories, items, 'items'));
I would make the categories as hash map and the key would be the id and iterate over all the items only.
then you get O(N) solution.
I was wondering if anyone could help figure out how i can set the value of a property in an object which can be found in a deep Array.
Below is an example of the Tree array
I would like to know how i can insert
var newObjectToInsert = {id: 999, name: 'new name'};
in the 'nodes' array of the object whose id === 3901
var tree = [
{
id: 1,
name: 'Level 1 - A',
nodes: [
{
id: 33,
name: 'Level 2 = A',
nodes: []
},
{
id: 21,
name: 'Level 2 = B',
nodes: []
}
]
},
{
id: 2,
name: 'Level 1 - B',
nodes: []
},
{
id: 3,
name: 'Level 1 - B',
nodes: [
{
id: 65,
name: 'Level 2 = A',
nodes: []
},
{
id: 124,
name: 'Level 2 = A',
nodes: [
{
id: 3901,
name: 'Level 3 - A'
},
{
id: 29182,
name: 'Level 3 - B',
nodes: [
{
id: 32423413,
name: 'Level 4 - A'
}
]
}
]
},
{
id: 534,
name: 'Level 2 = A',
nodes: []
}
]
},
];
You can use a native Array#some to achieve a recursive traversal. An advantage towards using this approach is it already provides a mechanism to stop the traversal once it finds the node that we want to insert the new object.
var inserted = tree.some(function cb(v) {
var nodes = v.nodes || [];
return v.id === nodeId?
(v.nodes = nodes).push(newObjectToInsert):
nodes.some(cb);
});
var tree = [{
id: 1,
name: 'Level 1 - A',
nodes: [{
id: 33,
name: 'Level 2 = A',
nodes: []
},
{
id: 21,
name: 'Level 2 = B',
nodes: []
}
]
},
{
id: 2,
name: 'Level 1 - B',
nodes: []
},
{
id: 3,
name: 'Level 1 - B',
nodes: [{
id: 65,
name: 'Level 2 = A',
nodes: []
},
{
id: 124,
name: 'Level 2 = A',
nodes: [{
id: 3901,
name: 'Level 3 - A'
},
{
id: 29182,
name: 'Level 3 - B',
nodes: [{
id: 32423413,
name: 'Level 4 - A'
}]
}
]
},
{
id: 534,
name: 'Level 2 = A',
nodes: []
}
]
},
];
var newObjectToInsert = {id: 999, name: 'new name'};
var nodeId = 3901;
var inserted = tree.some(function cb(v) {
var nodes = v.nodes || [];
return v.id === nodeId?
(v.nodes = nodes).push(newObjectToInsert):
nodes.some(cb);
});
console.log(tree);
body > div { min-height: 100%; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
You have a recursive structure. So you visit all items you need a visitor.
Pseudo code:
function visit(visitor, tree) {
visitor(tree);
items.nodes.forEach(subTree => visit(visitor, subTree));
}
And use
visit(node => {
if (node.id === 'whatever'){
node.push({yournode});
}
}, tree);