filter array based on multiple condition - javascript

I would like to get the result of my event based on the filter.
const filter = [
{ type: 'type', value: ['In Person'] },
{ type: 'city', value: ['Boston', 'Miami', 'New York'] },
];
const events = [
{ node: { city: 'Boston', type: 'In Person', name: 'Boston Party' } },
{ node: { city: 'New Jersey', type: 'In Person', name: 'Hello Cookie' } },
{ node: { city: 'Boston', type: 'Virtual', name: 'Sales Kick Off' } },
];
const result = events.filter(o => Object
.entries(o.node)
.every(([k, v]) => filter
.some(({ type, value }) => type === k && value.includes(v)),),)
console.log(result)
I want the first object of my events because filter contain Boston and In Person. (This function will work if I remove the key name on my events). How this function can return a result if i have more key and value on my events.

You could filter with filter.
const
filter = [{ type: 'type', value: ['In Person', 'Virtual'] }, { type: 'city', value: ['Boston', 'Miami', 'New York'] }],
events = [{ node: { city: 'Boston', type: 'In Person', name: 'Boston Party' } }, { node: { city: 'New Jersey', type: 'In Person', name: 'Hello Cookie' } }, { node: { city: 'Boston', type: 'Virtual', name: 'Sales Kick Off' } }],
result = events.filter(({ node }) =>
filter.every(({ type, value }) => !value.length || value.includes(node[type]))
);
console.log(result)
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

How to create Flat Array from tree data structure using node indexes?

I need to create a flat array using various hierarchical tree data. I'll get array of tree nodes as string in the form of parentNode-childNode-... This is all dynamic. I tried to convert treeNodeInput into nested object, but couldn't make use of that.
const TREE_DATA = [{
name: 'USA',
children: [{
name: 'Texas',
children: [{
name: 'Dallas'
}, {
name: 'Houston'
}]
}, {
name: 'California',
children: [{
name: 'Los Angeles'
}, {
name: 'San Francisco'
}]
}, {
name: 'New York',
children: [{
name: 'New York City'
}, {
name: 'Buffalo'
}]
}],
}, {
name: 'India',
children: [{
name: 'Bihar',
children: [{
name: 'Patna'
}, {
name: 'Gaya'
}],
}],
}, ];
let treeDataGroupedBy = ['Country', 'State', 'City'];
let treeNodeInput = ['0-0-0', '1-0-1'];
let data = []; /*Expected Result [{
Country: 'USA',
State: 'Texas',
City: 'Dallas'
}, {
Country: 'India',
State: 'Bihar',
City: 'Gaya'
}]*/
for (let item of treeNodeInput) {
let nodesArray = item.split("-");
let count = 0;
let rowObj = {};
for (let node of nodesArray) {
rowObj[treeDataGroupedBy[count]] = Object.keys(TREE_DATA)[Number(node)];
count++;
}
data.push(rowObj);
}
console.log(data)
You can get expected result by doing this minimum code change.
const TREE_DATA = [{
name: 'USA',
children: [{
name: 'Texas',
children: [{
name: 'Dallas'
}, {
name: 'Houston'
}]
}, {
name: 'California',
children: [{
name: 'Los Angeles'
}, {
name: 'San Francisco'
}]
}, {
name: 'New York',
children: [{
name: 'New York City'
}, {
name: 'Buffalo'
}]
}],
}, {
name: 'India',
children: [{
name: 'Bihar',
children: [{
name: 'Patna'
}, {
name: 'Gaya'
}],
}],
}, ];
let treeDataGroupedBy = ['Country', 'State', 'City'];
let treeNodeInput = ['0-0-0', '1-0-1'];
let data = []; /*Expected Result [{
Country: 'USA',
State: 'Texas',
City: 'Dallas'
}, {
Country: 'India',
State: 'Bihar',
City: 'Gaya'
}]*/
for (let item of treeNodeInput) {
let nodesArray = item.split("-");
let count = 0;
let rowObj = {};
let child = TREE_DATA
for (let node of nodesArray) {
if(child.hasOwnProperty('children')){
child =child["children"][node]
}
else{
child =child[node]
}
rowObj[treeDataGroupedBy[count]] = child.name;
count++;
}
data.push(rowObj);
}
console.log(data)
You can do this reccursively:
const TREE_DATA = [{
name: 'USA',
children: [{
name: 'Texas',
children: [{
name: 'Dallas'
}, {
name: 'Houston'
}]
}, {
name: 'California',
children: [{
name: 'Los Angeles'
}, {
name: 'San Francisco'
}]
}, {
name: 'New York',
children: [{
name: 'New York City'
}, {
name: 'Buffalo'
}]
}],
}, {
name: 'India',
children: [{
name: 'Bihar',
children: [{
name: 'Patna'
}, {
name: 'Gaya'
}],
}],
}, ];
let treeDataGroupedBy = ['Country', 'State', 'City'];
let treeNodeInput = ['0-0-0', '1-0-1'];
let data = [];
function LinearifyTree(tree, keys, path) {
let val = {keys[0]: tree[path[0]].name};
if (key.length === 1) {
return val;
}
return {...val, ...LinearifyTree(tree[path[0]].children, keys.slice(1), path.slice(1))};
}
for (let item of treeNodeInput) {
let nodeArray = item.split('-').map(v => Number(v));
data = LinearifyTree(TREE_DATA, treeDataGroupedBy, nodeArray);
}
console.log(data);
Is it optimal? No
Will it work? yes
Do you want it to be optimal? Change your data structure
Here is a recursive function to build that desired result.
const TREE_DATA=[{name:'USA',children:[{name:'Texas',children:[{name:'Dallas'},{name:'Houston'}]},{name:'California',children:[{name:'LosAngeles'},{name:'SanFrancisco'}]},{name:'NewYork',children:[{name:'NewYorkCity'},{name:'Buffalo'}]}],},{name:'India',children:[{name:'Bihar',children:[{name:'Patna'},{name:'Gaya'}],}],},];
let treeDataGroupedBy = ['Country', 'State', 'City'];
let result = [];
for (const data of TREE_DATA) {
buildResult(data, treeDataGroupedBy, 0, {}, result);
}
console.log(result);
function buildResult(data, treeDataGroupedBy, level, obj, result) {
if (!data || level >= treeDataGroupedBy.length) {
return;
}
const name = treeDataGroupedBy[level]; // 'Country', 'State', or 'City'
obj[name] = data.name;
if (!data.children) {
// No children exists, so no more levels down, so push the object to the result and return.
result.push(obj);
return;
}
for (let child of data.children) {
// Children exists, so visit each child. Increment level and pass object copy.
buildResult(child, treeDataGroupedBy, level + 1, {...obj}, result);
}
}

How do I create an array of objects with a nested array based on a similar key?

I have an array that looks something like this
const example = [
{ id: '1', name: 'Person 1', organization: { id: '11', name: 'Organization A' } },
{ id: '2', name: 'Person 2', organization: { id: '12', name: 'Organization A' } },
{ id: '3', name: 'Person 3', organization: { id: '13', name: 'Organization B' } },
];
As you can see, the organization name is something I want to key off of and create a data structure like this:
const output = [
// data.value will be their ID
{
organizationName: 'Organization A',
data: [
{ label: 'Person 1', value: '1' },
{ label: 'Person 2', value: '2' },
],
},
{
organizationName: 'Organization B',
data: [
{ label: 'Person 3', value: '3' },
],
},
]
What I've tried
I know I want to use reduce for something like this, but I feel like I'm off:
const providerOptions = externalPeople.data.reduce((acc, currentValue) => {
const {
organization: { name: organizationName },
} = currentValue;
if (organizationName) {
acc.push({ organization: organizationName, data: [] });
} else {
const { name: externalPersonName, id } = currentValue;
acc[acc.length - 1].data.push({ name: externalPersonName, value: id });
}
return acc;
}, [] as any);
However the output comes out to something like this:
[
{organizationName: 'Organization A', data: []},
{organizationName: 'Organization A', data: []},
{organizationName: 'Organization B', data: []},
];
data doesn't seem to get anything pushed inside the array in this reduce function, and the organization name get duplicated... what am I doing wrong?
Easiest way is to use an Map/Set/or object to keep track of orgs you create. This way you are not searching in the array to see if the organization was found already. After you are done, you can create the array you want from the object.
const externalPeople = {
data : [
{ id: '1', name: 'Person 1', organization: { id: '11', name: 'Organization A' } },
{ id: '2', name: 'Person 2', organization: { id: '12', name: 'Organization A' } },
{ id: '3', name: 'Person 3', organization: { id: '13', name: 'Organization B' } },
],
};
const providerOptions = Object.values(externalPeople.data.reduce((acc, currentValue) => {
const {
organization: { name: organizationName },
name: externalPersonName,
id
} = currentValue;
// Is the org new? Yes, create an entry for it
if (!acc[organizationName]) {
acc[organizationName] = { organization: organizationName, data: [] };
}
// push the person to the organization
acc[organizationName].data.push({ name: externalPersonName, value: id });
return acc;
}, {}));
console.log(providerOptions)
Here is another solution
const example = [
{ id: '1', name: 'Person 1', organization: { id: '11', name: 'Organization A' } },
{ id: '2', name: 'Person 2', organization: { id: '12', name: 'Organization A' } },
{ id: '3', name: 'Person 3', organization: { id: '13', name: 'Organization B' } },
];
const result = example.reduce((res, entry) => {
const recordIndex = res.findIndex(rec => rec.organizationName === entry.organization.name);
if(recordIndex >= 0) {
res[recordIndex].data.push({ label: entry.name, value: entry.id});
} else {
const record = {
organizationName: entry.organization.name,
data: [{ label: entry.name, value: entry.id }]
};
res.push(record);
}
return res;
}, []);
console.log(result);
You are not checking if the value is already present in your accumulation acc
You can check it with a simple find in the if statement since it's an array
const providerOptions = externalPeople.data.reduce((acc, currentValue) => {
const {
organization: { name: organizationName },
} = currentValue;
//Check if organization is not present already
if (!acc.find(a => a.organization === organizationName)) {
//Add also the data of the element your are processing
acc.push({ organization: organizationName, data: [{label: currentValue.name, value: currentValue.id}] });
} else {
const { name: externalPersonName, id } = currentValue;
acc[acc.length - 1].data.push({ label: externalPersonName, value: id });
}
return acc;
}, [] as any);
I also added the data of the first element of the group you create when adding the organization.
The result should be as your expected output:
[
{
organization: 'Organization A',
data: [
{ label: 'Person 1', value: '1' },
{ label: 'Person 2', value: '2' }
]
},
{
organization: 'Organization B',
data: [
{ label: 'Person 3', value: '3' }
]
}
]
Hope it helps!
Compare this solution (using Lodash) with other solutions. Which one emphasises your intentions at most? This is why we use Lodash in our company - to maintain code as declarative as we can, because code readability, with minimum cognitive overload, is most important goal during coding.
const persons = [
{ id: '1', name: 'Person 1', organization: { id: '11', name: 'Organization A' } },
{ id: '2', name: 'Person 2', organization: { id: '12', name: 'Organization A' } },
{ id: '3', name: 'Person 3', organization: { id: '13', name: 'Organization B' } },
];
const personsByOrganizations = _.groupBy(persons, 'organization.name')
const output = _.map(personsByOrganizations, (persons, organizationName) => ({
organizationName,
data: _.map(persons, ({ name, id }) => ({
label: name,
value: id
}))
}))
Something like that with using a Set?
result = [...new Set(example.map(d => d.organization.name))].map(label => {
return {
organizationName: label,
data: example.filter(d => d.organization.name === label).map(d => {
return {label: d.name, value: d.id}
})
}
})
`

Filter does not return the correct result

I have this array and I created this function that return me the filtered array:
const result = [{
key: 'A',
title: 'titleA',
data: [{
name: 'miael',
id: 'id4',
},
{
name: 'top',
id: 'id2',
}
]
},
{
key: 'B',
title: 'titleB',
data: [{
name: 'mich1',
id: 'id12',
},
{
name: 'tomato',
id: 'id123',
}
]
},
]
const doSearch = (data) => result.filter(entry =>
entry.data.some(item =>
item.name
.toString()
.toLowerCase()
.includes(data.toString().toLowerCase().trim()),
),
);
console.log(doSearch('mich'));
This works, but it also returns results that do not contain the searched word 'mic'
if I search for mic, I expect this result:
[{
key: 'B',
title: 'titleB',
data: [{
name: 'mich1',
id: 'id12',
},
]
}],
what am I doing wrong?
A couple of changes should make this work the way you wish.
Turning doSearch into a function.
Adding a searchFor parameter to the doSearch() function and passing to the .includes() call.
Using Array.reduce() to create the output array. Items are only added if they include the searchFor value.
const input = [{ key: 'A', title: 'titleA', data: [{ name: 'miael', id: 'id4', }, { name: 'top', id: 'id2', } ] }, { key: 'B', title: 'titleB', data: [{ name: 'mich1', id: 'id12', }, { name: 'tomato', id: 'id123', } ] }, ]
const doSearch = (searchFor, arr) => arr.reduce((acc, { key, title, data }) => {
const filteredData = data.filter(({ name }) => {
return name.toLowerCase().includes(searchFor.toLowerCase())
});
if (filteredData.length > 0) {
acc.push({ key, title, data: filteredData });
}
return acc;
}, []);
console.log(doSearch('mic', input ));
You can keep your current logic and add a map with the same filter for entry.data:
const result = [{
key: 'A',
title: 'titleA',
data: [{
name: 'miael',
id: 'id4',
},
{
name: 'top',
id: 'id2',
}
]
},
{
key: 'B',
title: 'titleB',
data: [{
name: 'mich1',
id: 'id12',
},
{
name: 'tomato',
id: 'id123',
}
]
},
]
function nameFilter(item, data) {
return item.name
.toString()
.toLowerCase()
.includes(data.toString().toLowerCase().trim())
}
const doSearch = (data) => result.filter(entry =>
entry.data.some(item =>
nameFilter(item, data)
),
).map(entry => ({
...entry,
data: entry.data.filter(item => nameFilter(item, data))
}));
console.log(doSearch('mich'));

Filter nested array of objects to array of objects

I have an array of objects which is nested. How do I make an array of objects with this one getting values from nested properties as well? If onClick property is empty, it means there is a property called children and the parent element should not be a part of the new list. I need to loop through the children array and get the values. Please see the expected output below.
const headers = [{
title: 'Arun',
id: 'arunId',
onClick: 'onClickArun'
},
{
title: "George",
id: 'georgeId',
onClick: '',
children: [{
title: 'David',
id: 'davidId',
onClick: 'onClickDavid'
},
{
title: 'Patrick',
id: 'patrickId',
onClick: 'onClickPatrick'
}
]
},
{
title: 'Mark',
id: 'markId',
onClick: 'onClickMark'
}
];
console.log(headers.map(item => {
return {
title: item.title,
onClick: item.onClick
}
}))
Expected Output:
[{
title: 'Arun',
onClick: 'onClickArun'
},
{
title: 'David',
onClick: 'onClickDavid'
},
{
title: 'Patrick',
onClick: 'onClickPatrick'
},
{
title: 'Mark',
onClick: 'onClickMark'
}
]
Any help is greatly appreciated.
You could take Array#flatMap with a recursive callback.
const
map = ({ title, onClick, children }) => onClick
? { title, onClick }
: children.map(map);
var headers = [{ title: 'Arun', id: 'arunId', onClick: 'onClickArun' }, { title: "George", id: 'georgeId', onClick: '', children: [{ title: 'David', id: 'davidId', onClick: 'onClickDavid' }, { title: 'Patrick', id: 'patrickId', onClick: 'onClickPatrick' }] }, { title: 'Mark', id: 'markId', onClick: 'onClickMark' }],
result = headers.flatMap(map);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can do this using Array.prototype.reduce, where you just need to check if the parent onClick prop is empty and the children prop is present or not:
var headers = [{ title: 'Arun', id: 'arunId', onClick: 'onClickArun' }, { title: "George", id: 'georgeId', onClick: '', children: [{ title: 'David', id: 'davidId', onClick: 'onClickDavid' }, { title: 'Patrick', id: 'patrickId', onClick: 'onClickPatrick' }] }, { title: 'Mark', id: 'markId', onClick: 'onClickMark' }];
function getObject(headers, acc){
return headers.reduce((acc, ele) => {
if(!ele.onClick.length && ele.children){
acc = getObject(ele.children, acc);
}else{
acc.push({"title": ele.title, "onClick": ele.onClick});
}
return acc;
}, acc);
}
console.log(getObject(headers, []));
Looks like you need a depth first search.
You run on the array, for each item with children recurse on it and pass along the existing array. otherwise, add the item to the list.
function getChildren(array, total = []){
for(let item of array) {
if(item.children) {
getChildren(item.children, total)
} else {
total.push({
title: item.title,
onClick: item.onClick
})
}
}
return total
}
let headers = [{
title: 'Arun',
id: 'arunId',
onClick: 'onClickArun'
},
{
title: "George",
id: 'georgeId',
onClick: '',
children: [{
title: 'David',
id: 'davidId',
onClick: 'onClickDavid'
},
{
title: 'Patrick',
id: 'patrickId',
onClick: 'onClickPatrick'
}
]
},
{
title: 'Mark',
id: 'markId',
onClick: 'onClickMark'
}
]
// take children, only if onClick is empty
.map(item => item.onClick ? item : item.children)
// make flat array
headers = [].concat.apply([], headers)
.map(item => {
const temp = {};
temp.title = item.title;
temp.onClick = item.onClick;
return temp; // take only onClick and title property from each item
})

format data using .map and .filter

i got a following type of result from the data base when i fetch database. i have tried many thing and serch google but could't found anything. please help me with this. thank you.
{ metaData:
[ { name: 'ID' },
{ name: 'NAME' },
{ name: 'LED_ID' },
{ name: 'LED_ORG_ID' },
{ name: 'COMPANY_ADD' },
{ name: 'STATE_CODE' },
{ name: 'CIN_NO' } ],
rows:
[ [ 1,
'company name',
2481,
'161',
'address ',
'27',
'number' ],
[ 2,
'company name2',
2581,
'164',
'address 2',
'27',
'number2' ]
}
}
I am trying to achieve below formatted data
{
data:[
{
ID:1,
NAME:'company name',
LED_ID:2481,
LED_ORG_ID: '161',
COMPANY_ADD:'address',
STATE_CODE:'27',
CIN_NO:'number'
},
{
ID:2,
NAME:'company name 2',
LED_ID:2581,
LED_ORG_ID: '164',
COMPANY_ADD:'address 2',
STATE_CODE:'27',
CIN_NO:'number 2'
}
]
}
You could get the keys first and then map the object from the entries.
var data = { metaData: [{ name: 'ID' }, { name: 'NAME' }, { name: 'LED_ID' }, { name: 'LED_ORG_ID' }, { name: 'COMPANY_ADD' }, { name: 'STATE_CODE' }, { name: 'CIN_NO' }], rows: [[1, 'company name', 2481, '161', 'address ', '27', 'number'], [2, 'company name2', 2581, '164', 'address 2', '27', 'number2']] },
keys = data.metaData.map(({ name }) => name),
result = { data: data.rows.map(a => Object.fromEntries(keys.map((k, i) => [k, a[i]]))) };
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Use map in conjunction with flatMap and reduce:
const metaData = [{name:'ID'},{name:'NAME'},{name:'LED_ID'},{name:'LED_ORG_ID'},{name:'COMPANY_ADD'},{name:'STATE_CODE'},{name:'CIN_NO'}];
const rows = [[1,'company name',2481,'161','address ','27','number'],[2,'company name2',2581,'164','address 2','27','number2']];
const res = rows.flatMap(e => e.map((f, i) => ({ [metaData[i].name]: f })).reduce((a, c) => ({ ...a, ...c }), {}));
console.log(res);
More performant solution thanks to Mark Meyer:
const res = rows.map(e => e.reduce((a, c, i) => ({ ...a, ...{ [metaData[i].name]: c }}), {});
You can use array.map() and Object.fromEntires():
let data = { metaData:
[ { name: 'ID' },
{ name: 'NAME' },
{ name: 'LED_ID' },
{ name: 'LED_ORG_ID' },
{ name: 'COMPANY_ADD' },
{ name: 'STATE_CODE' },
{ name: 'CIN_NO' } ],
rows:
[ [ 1,
'company name',
2481,
'161',
'address ',
'27',
'number' ],
[ 2,
'company name2',
2581,
'164',
'address 2',
'27',
'number2' ]
]
}
let result = data.rows.map(
entry => Object.fromEntries(
entry.map((x, i) => [data.metaData[i].name, x])
)
)
console.log(result)
EDIT: The outer map transforms rows so there will be two objects returned. The inner one transforms all the values into format like ["ID", 1]. That array of arrays is passed as an argument into Object.fromEntries which creates a new object based on those pairs.
let data = {
metaData: [{
name: 'ID'
},
{
name: 'NAME'
},
{
name: 'LED_ID'
},
{
name: 'LED_ORG_ID'
},
{
name: 'COMPANY_ADD'
},
{
name: 'STATE_CODE'
},
{
name: 'CIN_NO'
}
],
rows: [
[1,
'company name',
2481,
'161',
'address ',
'27',
'number'
],
[2,
'company name2',
2581,
'164',
'address 2',
'27',
'number2'
]
]
}
let transform = (meta, item) => {
return meta.map((a, i) => ({
[a.name]: item[i]
}))
}
let result = data.rows.map(i => transform(data.metaData, i))
console.log(result.map(i => Object.assign({}, ...i)))
It can be better...

Categories