Related
i have data like below,
const arr_obj = [
{
id: '1',
children: [],
type: 'TYPE1',
},
{
id: '2',
children: [
{
id: '1',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '2',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '3',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
]
type: 'TYPE2',
},
{
id: '3',
children: [
{
id: '4',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '5',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '6',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
]
type: 'TYPE2',
}
]
I have to find out the count of type: 'MAIN'. these 'MAIN' will be within type: "type2"
So the expected count is 6.
below is the code,
const ParentComponent = () => {
const findCount = (arr_obj) => {
let count = 0;
const expectedCount = 2;
const loop = (children) => {
for (const obj of children) {
const { type, children } = obj;
if (type === 'TYPE2') {
loop(children);
} else if (type === 'MAIN') {
++count;
if (count > expectedCount) return;
}
}
};
loop(children);
return count > expectedCount;
};
const output = findCount(arr_obj);
return (
//some jsx rendering
);
}
the above code works fine. but i want to make loop(children) function a pure function. I am not sure how to do it.
the problem now is i define variables outside the loop method.
how can i define everything as arguments to the function, you could move the function outside the component.
could someone help me with this. thanks.
You could take an array of the wanted type order and iterate only one level and han over the rest of wanted type. If no type are left over, return one otherwise the result of nested count.
const
getCount = (array, types) => {
let count = 0;
for (const { type, children } of array) {
if (types[0] === type) {
count += types.length === 1
? 1
: getCount(children, types.slice(1));
}
}
return count;
}
data = [{ id: '1', children: [], type: 'TYPE1' }, { id: '2', children: [{ id: '1', children: [{}], type: 'MAIN' }, { id: '2', children: [{}], type: 'MAIN' }, { id: '3', children: [{} ], type: 'MAIN' }], type: 'TYPE2' }, { id: '3', children: [{ id: '4', children: [{}], type: 'MAIN' }, { id: '5', children: [{}], type: 'MAIN' }, { id: '6', children: [{}], type: 'MAIN' }], type: 'TYPE2' }],
order = ['TYPE2', 'MAIN'],
count = getCount(data, order);
console.log(count);
Pure Function is a function (a block of code ) that always returns the same result if the same arguments are passed. It does not depend on any state, or data change during a program’s execution rather it only depends on its input arguments.
Reference
In the above shared code I could see expectedCount as the shared variable which is not the PURE function.
As I could see your comments the desired type is in Children then its just the 2 levels. Then the following code would work.
function count(data, condition) {
let count = 0;
data.forEach((value, index) => {
if(value.type === condition[0]){
value.children.forEach((val, idx) => {
if(val.type === condition[1]) {
count++;
}
})
}
});
return count;
}
const condition = ['TYPE2', 'MAIN'];
console.log(count(arr_obj, condition));
Nina's answer is more to the point but you could also do it by filtering the input array.
const data = [{ id: '1', children: [], type: 'TYPE1' }, { id: '2', children: [{ id: '1', children: [{}], type: 'MAIN' }, { id: '2', children: [{}], type: 'MAIN' }, { id: '3', children: [{} ], type: 'MAIN' }], type: 'TYPE2' }, { id: '3', children: [{ id: '4', children: [{}], type: 'MAIN' }, { id: '5', children: [{}], type: 'MAIN' }, { id: '6', children: [{}], type: 'MAIN' }], type: 'TYPE2' }];
const count = data
.filter(v=>v.type==='TYPE2')
.flatMap(v=>v.children)
.filter(v=>v.type==='MAIN')
.length
console.log(count);
This question already has answers here:
Dynamically set property of nested object
(28 answers)
Find by key deep in a nested array
(21 answers)
Closed last year.
Given an array like this, where the maximum depth can be 3 levels and where we don't know at what level the researched item could be:
const data = {
id: '1',
children: [
{
id: '2',
name: 'nameTest',
children: [
{
id: '3'
name: 'deepLevel'
}
]
}
}
how can I add a property to the third level knowing only the value 'deepLevel' ?
we are allowed to use lodash and strongly encouraged to use ES6.
the final dataStructure should be
Given an array like this, where the maximum depth can be of 3 levels:
const data = {
id: '1',
children: [
{
id: '2',
name: 'nameTest',
children: [
{
id: '3'
name: 'deepLevel'
addedProperty: true,
}
]
}
}
An approach was to separate the tasks of finding a nested item by a custom(izable) entry (key-value pair) and assigning additional custom data to the found item.
Thus one e.g. could implement two methods recursivelyFindItemByEntry which is based on self recursion and a simple assignToObjectWithFirstMatchingNestedEntry which assigns provided data to the result of the former function invocation ...
function recursivelyFindItemByEntry(obj, [key, value]) {
let item;
if (!!obj && (typeof obj === 'object')) {
if (
obj.hasOwnProperty(key) &&
(obj[key] === value)
) {
item = obj;
} else if (
obj.hasOwnProperty('children') &&
Array.isArray(obj.children)
) {
obj.children.some(child => {
item = recursivelyFindItemByEntry(child, [key, value]);
return !!item;
});
}
}
return item;
}
function assignToObjectWithFirstMatchingNestedEntry(obj, [key, value], data) {
Object.assign(
recursivelyFindItemByEntry(obj, [key, value]) ?? {},
data ?? {}
);
return obj;
}
const data = {
id: '1',
children: [{
id: '2',
name: 'nameTest',
children: [{
id: '3',
name: 'deepLevel',
}, {
id: '4',
name: 'deepLevel',
}],
}, {
id: '5',
name: 'nameTest',
children: [{
id: '6',
name: 'deepLevel',
}, {
id: '7',
name: 'deepLevelTarget',
// addedProperty: true,
}, {
id: '8',
name: 'deepLevel',
}],
}, {
id: '9',
name: 'nameTest'
}, {
id: '10',
name: 'nameTestTarget'
}, {
id: '11',
name: 'nameTest'
}],
};
console.log(
"recursivelyFindItemByEntry(data, ['name', 'deepLevelTarget']) ...",
recursivelyFindItemByEntry(data, ['name', 'deepLevelTarget'])
);
console.log(
"recursivelyFindItemByEntry(data, ['id', '10']) ...",
recursivelyFindItemByEntry(data, ['id', '10'])
);
console.log('\n');
console.log(
"recursivelyFindItemByEntry(data, ['id', 'foo']) ...",
recursivelyFindItemByEntry(data, ['id', 'foo'])
);
console.log(
"recursivelyFindItemByEntry(data, ['id', '1']) ...",
recursivelyFindItemByEntry(data, ['id', '1'])
);
console.log('\n');
console.log(
"assignToObjectWithFirstMatchingNestedEntry(data, ['name', 'deepLevelTarget']), { addedProperty: true } ...",
assignToObjectWithFirstMatchingNestedEntry(data, ['name', 'deepLevelTarget'], { addedProperty: true })
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
I have a array of object which has a inside array which need to be filtered and return array based on matches from both. search is (input) event, which executes on every key press.
stackblitz link stackblitz
list = [
{
id: 'abc',
data: [
{ key: '1', value: 'car' },
{ key: '2', value: 'bus' },
{ key: '3', value: 'bike' },
{ key: '4', value: 'truck' },
{ key: '5', value: 'jeep' },
],
},
{
id: 'def',
data: [
{ key: '1', value: 'car' },
{ key: '2', value: 'bicycle' },
{ key: '3', value: 'train' },
{ key: '4', value: 'aeroplane' },
{ key: '5', value: 'jeep' },
],
},
];
handleSearch = (event) => {
if (event.target.value.length > 0) {
const item = this.list[0].data.filter((items) =>
items.value.toLowerCase().includes(event.target.value.toLowerCase())
);
this.list[0].data = item;
} else {
this.list[0].data = this.orgList;
}
};
expect output
input = car
output = [
{
id: 'abc',
data: [
{ key: '1', value: 'car' },
],
},
{
id: 'def',
data: [
{ key: '1', value: 'car' },
],
},
];
input = truck
output =
[
{
id: 'abc',
data: [
{ key: '4', value: 'truck' },
],
},
];
const list = [{id: 'abc',data: [{ key: '1', value: 'car' },{ key: '2', value: 'bus' },{ key: '3', value: 'bike' },{ key: '4', value: 'truck' },{ key: '5', value: 'jeep' },],},{id: 'def',data: [{ key: '1', value: 'car' },{ key: '2', value: 'bicycle' },{ key: '3', value: 'train' },{ key: '4', value: 'aeroplane' },{ key: '5', value: 'jeep' },],},];
function search(arr, searchVal) {
return arr.map((item) => {
const data = item.data.filter(({ value }) => value === searchVal);
return { ...item, data };
})
.filter(({ data }) => data.length);
}
console.log(search(list, 'car'));
console.log(search(list, 'truck'));
.as-console-wrapper { max-height: 100% !important; top: 0 }
Angular demo
I know that I might be going a bit outside of the scope of your requirements here, but I just simply thought that it might be easier to do it like this.
I just thought that it might be somewhat more scalable this way, if you first flatten the structure, because for arguments sake, let's say that you're data structure needs to become more & more complex overtime, IDK, business requirements change. At least if you have some layer of abstraction to manage that, you can then filter on an array of objects quite simply, like I've done below.
Depending on your needs you may not even need to flatten the structure, it's just my opinion & experience states this to be an easier & more maintainable kinda solution to scale. If you're data structure dose evolve with complexity, where there may be nested structures, you could always look at using some clever little recursive function to flatten your structure.
It's worth also noting that I've added some validation to the search function, while it's probably not a requirement, it's not a bad idea to include such logic, where you could update state on your view model. You could include something like a toast notification, stating that the user has provided an invalid search term, you could be making a request to a server to get this data & you could say that there were no results, etc, I think you get the idea?
I hope that's helped & I'm sorry if I've gone a little OTT. 😅
const list = [
{
id: 'abc',
data: [
{ key: '1', value: 'car' },
{ key: '2', value: 'bus' },
{ key: '3', value: 'bike' },
{ key: '4', value: 'truck' },
{ key: '5', value: 'jeep' },
],
},
{
id: 'def',
data: [
{ key: '1', value: 'car' },
{ key: '2', value: 'bicycle' },
{ key: '3', value: 'train' },
{ key: '4', value: 'aeroplane' },
{ key: '5', value: 'jeep' },
],
},
];
const flattenStructure = data => {
return data.reduce((accumulator, item) => {
const items = item.data.reduce((vehicles, vehicle) => {
const modified = { ...vehicle, id: item.id };
return vehicles.concat(modified);
}, []);
return accumulator.concat(items);
}, []);
};
const search = (array, term) => {
const invalidTerm = term == null || typeof term != 'string' || term.replace(/ /g, '') == '';
const invalidArray = array == null || !Array.isArray(array);
if (invalidTerm || invalidArray) {
console.log("Invalid arguments provided.");
return array;
}
return flattenStructure(array).filter(vehicle => {
const match = vehicle.value.toLowerCase() == term.toLowerCase();
const contains = vehicle.value.toLowerCase().indexOf(term.toLowerCase()) > -1;
return match || contains;
});
};
console.log(search(list, 'car'));
console.log(search(list, 'truck'));
Generaly speaking, when dealing with filtering, avoid using same original array to display filtered results in template.
Concerning filtering function, this should do the trick:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
public list: any;
public orgList: any;
public filteredList: any;
ngOnInit() {
this.list = this.orgList = [
{
id: 'abc',
data: [
{ key: '1', value: 'car' },
{ key: '2', value: 'bus' },
{ key: '3', value: 'bike' },
{ key: '4', value: 'truck' },
{ key: '5', value: 'jeep' },
],
},
{
id: 'def',
data: [
{ key: '1', value: 'car' },
{ key: '2', value: 'bicycle' },
{ key: '3', value: 'train' },
{ key: '4', value: 'aeroplane' },
{ key: '5', value: 'jeep' },
],
},
];
}
filterData = (dataItem, term: string) => {
return dataItem.value.toLowerCase().indexOf(term.toLowerCase()) !== -1;
};
handleSearch = (event) => {
if (event.target.value.length === 0) {
this.filteredList = [];
return;
}
const term = event.target.value;
const temp = this.list.filter((fullItem) =>
fullItem.data.filter((dataItem) => this.filterData(dataItem, term))
);
this.filteredList = temp
.map((fullItem) => ({
...fullItem,
data: fullItem.data.filter((dataItem) =>
this.filterData(dataItem, term)
),
}))
.filter((fullItem) => fullItem.data.length > 0);
};
}
I have an array like this. How to group all child Ids into an array?
My solution below is not giving me all child elements. Where is the mistake? and suggest me any other ways
const data = {
name: '1',
id: '05f770d5',
child: [
{
name: '2',
id: '0ecfc8e1',
child: [
{
name: '3',
id: '2e1eb75c',
child: [],
},
],
},
{
name: '1c',
id: 'b9ee9864',
child: [
{
name: '8',
id: '575f4760',
child: [],
},
],
},
],
};
let array1 = [];
function sumChild(data) {
data.child.forEach((data) => {
array1.push(data.id);
sumChild(data?.child[0]);
});
return array1;
}
sumChild(data);
console.log(array1);
function sumChild(data) {
data.child?.forEach((data) => {
array1.push(data.id);
sumChild(data);
});
return array1;
}
I'm currently trying to retrieve a list of metadata stored as an array, inside an object, inside an array. Here's a better explanatory example:
[
{
name: 'test',
metadata: [
{
name: 'Author',
value: 'foo'
},
{
name: 'Creator',
value: 'foo'
}
]
},
{
name: 'otherTest',
metadata: [
{
name: 'Created',
value: 'foo'
},
{
name: 'Date',
value: 'foo'
}
]
},
{
name: 'finalTest'
}
]
Now, my objective is to retrieve a list of metadata (by their name) without redundancy. I think that .map() is the key to success but I can't find how to do it in a short way, actually my code is composed 2 for and 3 if, and I feel dirty to do that.
The expected input is: ['Author', 'Creator', 'Created', 'Date']
I'm developping in Typescript, if that can help for some function.
You can use reduce() and then map() to return array of names.
var data = [{"name":"test","metadata":[{"name":"Author","value":"foo"},{"name":"Creator","value":"foo"}]},{"name":"otherTest","metadata":[{"name":"Created","value":"foo"},{"name":"Date","value":"foo"}]},{"name":"finalTest"}]
var result = [...new Set(data.reduce(function(r, o) {
if (o.metadata) r = r.concat(o.metadata.map(e => e.name))
return r
}, []))];
console.log(result)
You could use Set for unique names.
var data = [{ name: 'test', metadata: [{ name: 'Author', value: 'foo' }, { name: 'Creator', value: 'foo' }] }, { name: 'otherTest', metadata: [{ name: 'Created', value: 'foo' }, { name: 'Date', value: 'foo' }] }, { name: 'finalTest' }],
names = new Set;
data.forEach(a => (a.metadata || []).forEach(m => names.add(m.name)));
console.log([...names]);
.as-console-wrapper { max-height: 100% !important; top: 0; }
var data = [{"name":"test","metadata":[{"name":"Author","value":"foo"},{"name":"Creator","value":"foo"}]},{"name":"otherTest","metadata":[{"name":"Created","value":"foo"},{"name":"Date","value":"foo"}]},{"name":"finalTest"}]
data
.filter(function(obj){return obj.metadata != undefined})
.map(function(obj){return obj.metadata})
.reduce(function(a,b){return a.concat(b)},[])
.map(function(obj){return obj.name})
A hand to hand Array.prototype.reduce() and Array.prototype.map() should do it as follows;
var arr = [
{
name: 'test',
metadata: [
{
name: 'Author',
value: 'foo'
},
{
name: 'Creator',
value: 'foo'
}
]
},
{
name: 'otherTest',
metadata: [
{
name: 'Created',
value: 'foo'
},
{
name: 'Date',
value: 'foo'
}
]
},
{
name: 'finalTest'
}
];
result = arr.reduce((p,c) => c.metadata ? p.concat(c.metadata.map(e => e.name))
: p, []);
console.log(result);