Multiple Dynamic Filters in JavaScript Array - javascript

I can't seem to think about how I can overcome this issue where there might be any amount of filters as objects which will help me to filter out the data array.
data = [
{
id: 1,
first_name: 'Colver',
}, {
id: 2,
first_name: 'Brodie',
}, {
id: 3,
first_name: 'Philippa',
}, {
id: 4,
first_name: 'Taite',
}, {
id: 5,
first_name: 'Pierson'
}
];
filters = [
{
field: 'id',
operator: 'between',
value: '2-5'
},
{
field: 'first_name',
operator: 'eq',
value: 'Philippa'
}
];
ngOnInit(): void {
const filteredItems = [];
this.data.forEach(item => {
this.filters.forEach((filter, filterIndex) => {
const itemValue = item[filter.field];
switch (filter.operator) {
case 'eq':
if (itemValue === filter.value) {
filteredItems.push(item);
}
break;
case 'between':
const [firstValue, secondValue] = filter.value.split('-');
if (itemValue > firstValue && itemValue < secondValue) {
filteredItems.push(item);
}
break;
}
});
});
console.log(filteredItems);
}
I basically want the filteredItems to output like below since the id is between 2 and 5 and the first_name is Philippa. But since I'm iterating the filters 2 times both the times items gets pushed to filteredItems.
[{
id: 3,
first_name: 'Philippa',
}]

You could take Array#every and an object for getting the right operator function.
const
data = [{ id: 1, first_name: 'Colver' }, { id: 2, first_name: 'Brodie' }, { id: 3, first_name: 'Philippa' }, { id: 4, first_name: 'Taite' }, { id: 5, first_name: 'Pierson' }],
filters = [{ field: 'id', operator: 'between', value: '2-5' }, { field: 'first_name', operator: 'eq', value: 'Philippa' }],
operators = {
between: (field, range) => {
const [min, max] = range.split('-').map(Number);
return min <= field && field <= max;
},
eq: (field, value) => field === value
},
result = data.filter(o =>
filters.every(({ field, operator, value }) =>
operators[operator](o[field], value)
)
);
console.log(result);

You can perform a reduce operation over the filters array and use Array#filter to remove objects on each iteration.
const data = [
{
id: 1,
first_name: 'Colver',
}, {
id: 2,
first_name: 'Brodie',
}, {
id: 3,
first_name: 'Philippa',
}, {
id: 4,
first_name: 'Taite',
}, {
id: 5,
first_name: 'Pierson'
}
],
filters = [
{
field: 'id',
operator: 'between',
value: '2-5'
},
{
field: 'first_name',
operator: 'eq',
value: 'Philippa'
}
];
const res = filters.reduce((acc,{field,operator,value})=>
acc.filter(o => operator === 'eq' && o[field] === value ||
operator === 'between' && o[field] >= value.split('-')[0]
&& o[field] <= value.split('-')[1]), data);
console.log(res);

Use Array.prototype.every to make sure every filter passes, and if so, push it to the array:
ngOnInit(): void {
const filteredItems = this.data.forEach(item =>
this.filters.every((filter, filterIndex) => {
const itemValue = item[filter.field];
switch (filter.operator) {
case 'eq':
if (itemValue === filter.value) {
return true;
}
break;
case 'between':
const [firstValue, secondValue] = filter.value.split('-');
if (itemValue > firstValue && itemValue < secondValue) {
return true;
}
break;
}
return false;
})
);
console.log(filteredItems);
}

Instead of using
const filteredItems = [];
this.data.forEach(item => {
// [...]
filteresItems.push(item)
// [...]
});
use Array's filter:
const filteredItems = this.data.filter(item => {
// [...]
let match = true; // or false
return match;
});
Taking your whole example, you could use:
function passes(item, filter) {
const itemValue = item[filter.field];
switch (filter.operator) {
case 'eq':
if (itemValue === filter.value) {
return true;
}
case 'between':
const [firstValue, secondValue] = filter.value.split('-');
if (itemValue > firstValue && itemValue < secondValue) {
return true;
}
}
return false;
}
const filteredItems = this.data.filter(
item => this.filters
.map(filter => passes(item, filter))
.every());

Related

Can this algorithm be made more efficient?

Currently I have an algorithm that runs to compare to different arrays of objects.
const allGroups = [{ id: '12345', name: 'groupOne'}, {id: '23421', name: 'groupTwo'},
{id: '28182', name: 'groupThree'}]
const clientsGroups = [{ id: 'abcde', clientGroupID: '12345'}, {id: 'dfcdae', clientGroupID: '93282'},
{id: 'jakdab', clientGroupID: '28182'}, {id: 'oiewad', clientGroupID: '93482'}]
const updateClientGroups = (allGroups, clientsGroups) => {
let allGroupsCopy = [...allGroups];
for (let i = 0; i < allGroupsCopy.length; i++) {
const allGroupsId = allGroupsCopy[i].id;
for (let j = 0; j < clientsGroups.length; j++) {
if (allGroupsId === clientsGroups[j].clientGroupID) {
allGroupsCopy[i] = {
...allGroupsCopy[i],
inGroup: true,
clientGroupID: clientsGroups[j].id,
};
}
}
}
return allGroupsCopy;
};
I check two different arrays of objects, if the id of allGroups matches the clientGroupID of clientGroups, I mutate the 'allGroupsCopy' to have 'inGroup: true' and add in the id of the clientsGroups.
The problem with this algorithm is it runs in n^2 time. Is there a more efficient way to do this?
Without changing the original arrays, could this be the an optimization ?
const allGroups = [
{ id: "12345", name: "groupOne" },
{ id: "23421", name: "groupTwo" },
{ id: "28182", name: "groupThree" },
];
const clientsGroups = [
{ id: "abcde", clientGroupID: "12345" },
{ id: "dfcdae", clientGroupID: "93282" },
{ id: "jakdab", clientGroupID: "28182" },
{ id: "oiewad", clientGroupID: "93482" },
];
const updateClientGroups = (groups, clients) => {
return clients.reduce((acum, current) => {
const isInGroup = groups.find((group) => group.id === current.clientGroupID);
acum.push({
...current,
inGroup: Boolean(isInGroup),
});
return acum;
}, []);
};
updateClientGroups(allGroups, clientsGroups)
If you change allGroups structure from array to map, you can do the job in linear time.
Something like:
const allGroups = {
'12345': { id: '12345', name: 'groupOne'}
...
}
const updateClientGroups = (allGroups, clientsGroups) => {
const clientGroupsMap = {};
clientsGroups.forEach(({clientGroupID}) =>
if(allGroups[clientGroupID]) {
clientGroupsMap[clientGroupID] = {...allGroups[clientGroupID], inGroup: true};
}
);
return {...allGroups, ...clientGroupsMap};
};

Return similar values ​from multiple array of objects in Javascript

Guys I made a simple example to illustrate my problem. I have 3 object arrays, datasOne, datasTwo and datasThree and what I want is to return a new array only with the objects that are in the 3 arrays. For example, if there is only Gustavo in the 3 arrays, then he will be returned. But there is a detail that if the datasThree is an empty array, then it will bring the data in common only from datasOne and datasTwo and if only the datasTwo which has data and the other two arrays have empty, then it will return data only from datasTwo. In other words it is to return similar data only from arrays that have data. I managed to do this algorithm and it works the way I want, but I would like to know another way to make it less verbose and maybe simpler and also work in case I add more arrays to compare like a dataFour for example. I appreciate anyone who can help me.
My code below:
let datasOne = [
{ id: 1, name: 'Gustavo' },
{ id: 2, name: 'Ana' },
{ id: 3, name: 'Luiz' },
{ id: 8, name: 'Alice' }
]
let datasTwo = [
{ id: 1, name: 'Gustavo' },
{ id: 3, name: 'Luiz' },
{ id: 8, name: 'Alice' }
]
let datasThree = [
{ id: 1, name: 'Gustavo' },
{ id: 3, name: 'Luiz' },
{ id: 2, name: 'Ana' },
{ id: 5, name: 'Kelly' },
{ id: 4, name: 'David' }
]
let filtered
if (datasOne.length > 0 && datasTwo.length > 0 && datasThree.length > 0) {
filtered = datasOne.filter(firstData => {
let f1 = datasThree.filter(
secondData => firstData.id === secondData.id
).length
let f2 = datasTwo.filter(
secondData => firstData.id === secondData.id
).length
if (f1 && f2) {
return true
}
})
} else if (datasOne.length > 0 && datasTwo.length > 0) {
filtered = datasOne.filter(firstData => {
return datasTwo.filter(secondData => firstData.id === secondData.id).length
})
} else if (datasOne.length > 0 && datasThree.length > 0) {
filtered = datasOne.filter(firstData => {
return datasThree.filter(secondData => firstData.id === secondData.id)
.length
})
} else if (datasTwo.length > 0 && datasThree.length > 0) {
filtered = datasTwo.filter(firstData => {
return datasThree.filter(secondData => firstData.id === secondData.id)
.length
})
} else if (datasThree.length > 0) {
filtered = datasThree
} else if (datasTwo.length > 0) {
filtered = datasTwo
} else if (datasOne.length) {
filtered = datasOne
}
console.log(filtered)
1) You can first filter the array which is not empty in arrs.
const arrs = [datasOne, datasTwo, datasThree].filter((a) => a.length);
2) Flatten the arrs array using flat().
arrs.flat()
3) Loop over the flatten array and count the occurrence of all objects using Map
const map = new Map();
for (let o of arrs.flat()) {
map.has(o.id)
? (map.get(o.id).count += 1)
: map.set(o.id, { ...o, count: 1 });
}
4) Loop over the map and collect the result only if it is equal to arrs.length
if (count === arrs.length) result.push(rest);
let datasOne = [
{ id: 1, name: "Gustavo" },
{ id: 2, name: "Ana" },
{ id: 3, name: "Luiz" },
{ id: 8, name: "Alice" },
];
let datasTwo = [
{ id: 1, name: "Gustavo" },
{ id: 3, name: "Luiz" },
{ id: 8, name: "Alice" },
];
let datasThree = [
{ id: 1, name: "Gustavo" },
{ id: 3, name: "Luiz" },
{ id: 2, name: "Ana" },
{ id: 5, name: "Kelly" },
{ id: 4, name: "David" },
];
const arrs = [datasOne, datasTwo, datasThree].filter((a) => a.length);
const map = new Map();
for (let o of arrs.flat()) {
map.has(o.id)
? (map.get(o.id).count += 1)
: map.set(o.id, { ...o, count: 1 });
}
const result = [];
for (let [, obj] of map) {
const { count, ...rest } = obj;
if (count === arrs.length) result.push(rest);
}
console.log(result);
/* This is not a part of answer. It is just to give the output fill height. So IGNORE IT */
.as-console-wrapper { max-height: 100% !important; top: 0; }
Not 100% sure it cover all edge cases, but this might get you on the right track:
function filterArrays(...args) {
const arraysWithData = args.filter((array) => array.length > 0);
const [firstArray, ...otherArrays] = arraysWithData;
return firstArray.filter((item) => {
for (const array of otherArrays) {
if (!array.some((itemTwo) => itemTwo.id === item.id)) {
return false;
}
}
return true;
});
}
Usage:
const filtered = filterArrays(datasOne, datasTwo, datasThree);
console.log(filtered)
I believe the code is fairly readable, but if something is not clear I'm glad to clarify.
function merge(arr){
arr = arr.filter(item=>item.length>0)
const map = {};
arr.forEach(item=>{
item.forEach(obj=>{
if(!map[obj.id]){
map[obj.id]=[0,obj];
}
map[obj.id][0]++;
})
})
const len = arr.length;
const ret = [];
Object.keys(map).forEach(item=>{
if(map[item][0]===len){
ret.push(map[item][1])
}
})
return ret;
}
merge([datasOne,datasTwo,datasThree])

compare two array of objects and get difference

Mock
a = [{id:123},{id:1234},{id:12345}]
b = [{id:123},{id:1234},{id:123456}]
Code
a.filter((element)=> {
return b.some((ele) =>{
if (element.id === ele.id) {
return matched[element.id] = element.id
} else {
return unmatched[element.id] = element.id
}
});
});
Expected output
matched = {123: 123, 1234: 1234}
unmatched = {12345: 12345}
output
unmatched = {123: 123, 1234: 1234, 12345: 12345}
matched = {123: 123, 1234: 1234}
could any one help me out here. I am trying to compare two arrays and get the difference into different objects
You could take a Set or object for a and iterate b for getting the poperty into the right object.
const
a = [{ id: 123 }, { id: 1234 }, { id: 12345 }],
b = [{ id: 123 }, { id: 1234 }, { id: 123456 }],
aSet = new Set(a.map(({ id }) => id)),
[matched, unmatched] = b.reduce((r, { id }) => {
Object.assign(r[1 - aSet.has(id)], { [id]: id });
return r;
}, [{}, {}]);
console.log(matched);
console.log(unmatched);
.as-console-wrapper { max-height: 100% !important; top: 0; }
An approach by using the objects directly
const
a = [{ _id: '123', index: 3 }, { _id: '1234', index: 3 }],
b = [{ _id: '123', index: 2 }, { _id: '12345', index: 3 }],
aSet = new Set(a.map(({ _id }) => _id)),
[matched, unmatched] = b.reduce((r, o) => {
r[1 - aSet.has(o._id)].push(o);
return r;
}, [[], []]);
console.log(matched);
console.log(unmatched);
.as-console-wrapper { max-height: 100% !important; top: 0; }
This would work:
a = [{id:123},{id:1234},{id:12345}];
b = [{id:123},{id:1234},{id:123456}];
function compare(a, b) {
const returnObj = { matched: [], unmatched: [] };
a.forEach(aItem => {
const found = b.find(bItem => JSON.stringify(aItem) === JSON.stringify(bItem));
returnObj[found ? 'matched' : 'unmatched'].push(aItem);
});
return returnObj;
}
const { matched, unmatched } = compare(a, b);
console.log(matched);
console.log(unmatched);

Getting occurrences of different values on nested object

I've an array of objects like this:
arrObj = [{
id: 1
data: {
info: {
name: 'jhon'
}
}
},{
id: 1
data: {
info: {
name: 'jane'
}
}
},{
id: 1
data: {
info: {
name: 'jhon'
}
}
}]
And I needs get a summary of occurrences for different values, like this:
{ jane: 1, jhon: 2 }
The big problem is that I need pass the nested prop dynamically:
getSummary('data.info.name',obj) //--> { jane: 1, jhon: 2 }
Any ideas?
You can use the below code, this is just hint. you need to do error handling if some input is not having correct nested keys.
let arrObj = [{
id: 1,
data: {
info: {
name: 'jhon'
}
}
},{
id: 1,
data: {
info: {
name: 'jane'
}
}
},{
id: 1,
data: {
info: {
name: 'jhon'
}
}
}]
const getSummary = (dynamicKeys,obj) => {
const list = dynamicKeys.split('.');
const op = {};
for (let i = 0; i < obj.length; i++) {
let n = 1, key = obj[i][list[0]];
while (list.length > n) {
key = key[list[n]];
n++;
}
op[key] = op[key] ? op[key] + 1 : 1;
}
return op;
}
const test = getSummary('data.info.name', arrObj);
console.log(test)
A possible solution could be as below. Here at first given prop is found out from each element of arrayObj. If the finding isn't successful, the element is skipped and move to next. When the finding is successful, append the finding value to summary if it does not exist in summary or increment the existing value. You can change the code as your requirements.
const arrObj = [{
id: 1,
data: {
info: {
name: 'jhon'
}
}
}, {
id: 1,
data: {
info: {
name: 'jane'
}
}
}, {
id: 1,
data: {
info: {
name: 'jhon'
}
}
}];
const getSummary = (prop, arr) => {
const keys = prop.split('.');
const findPropValue = (elem) =>
keys.reduce((val, key, index) => {
if (index === 0) return elem[key];
return (val && val[key]) || val
}, null);
return arr.reduce((sum, curr) => {
const key = findPropValue(curr);
if (!key) return sum;
sum[key] = (sum[key] && sum[key] + 1) || 1;
return sum;
}, {});
};
console.log(getSummary('data.info.name', arrObj));
Go over elements using forEach. For each object, access the value and build a res object with keys as value (eg jane) and object values are aggregated.
[Access the value, by split the path, access object nested using reduce)
const getSummary = (path, items) => {
const paths = path.split(".");
const res = {};
items.forEach((item) => {
const value = paths.reduce((acc, cur) => acc[cur], item);
res[value] = (res[value] ?? 0) + 1;
});
return res;
};
arrObj = [
{
id: 1,
data: {
info: {
name: "jhon",
},
},
},
{
id: 1,
data: {
info: {
name: "jane",
},
},
},
{
id: 1,
data: {
info: {
name: "jhon",
},
},
},
];
const output = getSummary("data.info.name", arrObj);
console.log(output);

JavaScript remove duplicate object and access data from removed object and append

var array = [{
id: "decafc0ffeefacedbabef00ddeadbeef",
long_id: "1;;decafc0ffeefacedbabef00ddeadbeef"
}, {
id: "4bb6ac319db42fabab84826a1c08e8da",
long_id: "1;;decafc0ffeefacedbabef00ddeadbeef;;47421d5c40b2f15d801ac6ca0ff4e6cd;;4bb6ac319db42fabab84826a1c08e8da"
}, {
id: "4bb6ac319db42fabab84826a1c08e8da",
long_id: "1;;decafc0ffeefacedbabef00ddeadbeef;;4ace8bd1ec354275a813d6e3725047c0;;4bb6ac319db42fabab84826a1c08e8da"
}, {
id: "47421d5c40b2f15d801ac6ca0ff4e6cd",
long_id: "1;;decafc0ffeefacedbabef00ddeadbeef;;47421d5c40b2f15d801ac6ca0ff4e6cd"
}, {
id: "4ace8bd1ec354275a813d6e3725047c0",
long_id: "1;;decafc0ffeefacedbabef00ddeadbeef;;4ace8bd1ec354275a813d6e3725047c0"
}];
var keyToBeUnique = 'id';
var newarray = array.filter((val, key) => {
return !array.slice(key + 1)
.some((valNew) => {
if(valNew[keyToBeUnique] === val[keyToBeUnique])
valNew['long_id'] = val['long_id'] +','+ valNew['long_id'];
return valNew[keyToBeUnique] === val[keyToBeUnique];
})
});
console.log(newarray);
Looking for a better way to append the long_Id "valNew['long_id'] = val['long_id'] +','+ valNew['long_id'];" which is written inside "some" function
you can get your result using reduce, it is simplier to what you want to achieve.
var array = [{
id: "decafc0ffeefacedbabef00ddeadbeef",
long_id: "1;;decafc0ffeefacedbabef00ddeadbeef"
}, {
id: "4bb6ac319db42fabab84826a1c08e8da",
long_id: "1;;decafc0ffeefacedbabef00ddeadbeef;;47421d5c40b2f15d801ac6ca0ff4e6cd;;4bb6ac319db42fabab84826a1c08e8da"
}, {
id: "4bb6ac319db42fabab84826a1c08e8da",
long_id: "1;;decafc0ffeefacedbabef00ddeadbeef;;4ace8bd1ec354275a813d6e3725047c0;;4bb6ac319db42fabab84826a1c08e8da"
}, {
id: "47421d5c40b2f15d801ac6ca0ff4e6cd",
long_id: "1;;decafc0ffeefacedbabef00ddeadbeef;;47421d5c40b2f15d801ac6ca0ff4e6cd"
}, {
id: "4ace8bd1ec354275a813d6e3725047c0",
long_id: "1;;decafc0ffeefacedbabef00ddeadbeef;;4ace8bd1ec354275a813d6e3725047c0"
}];
function reducer(array, keyToBeUnique) {
return array.reduce((accum, cv) => {
const index = accum.findIndex(item => item[keyToBeUnique] === cv[keyToBeUnique])
// if the index is -1 it means you dont have that ID yet, then push it.
if (index === -1) {
accum.push(cv)
} else {
// if it is not -1 you can edit the long_id property and add your strings.
accum[index]['long_id'] = accum[index]['long_id'] + ', ' + cv['long_id'];
}
return accum;
}, []);
}
console.log(reducer(array, 'id'));
you can use .map() to extract the ids and a new Set() to remove the duplicates, then remap the resulting array to put back the elements from the original one :
var array = [{
id: "decafc0ffeefacedbabef00ddeadbeef",
long_id: "1;;decafc0ffeefacedbabef00ddeadbeef"
}, {
id: "4bb6ac319db42fabab84826a1c08e8da",
long_id: "1;;decafc0ffeefacedbabef00ddeadbeef;;47421d5c40b2f15d801ac6ca0ff4e6cd;;4bb6ac319db42fabab84826a1c08e8da"
}, {
id: "4bb6ac319db42fabab84826a1c08e8da",
long_id: "1;;decafc0ffeefacedbabef00ddeadbeef;;4ace8bd1ec354275a813d6e3725047c0;;4bb6ac319db42fabab84826a1c08e8da"
}, {
id: "47421d5c40b2f15d801ac6ca0ff4e6cd",
long_id: "1;;decafc0ffeefacedbabef00ddeadbeef;;47421d5c40b2f15d801ac6ca0ff4e6cd"
}, {
id: "4ace8bd1ec354275a813d6e3725047c0",
long_id: "1;;decafc0ffeefacedbabef00ddeadbeef;;4ace8bd1ec354275a813d6e3725047c0"
}];
const filterArray = (array, key) => {
const deduped = [...new Set(array.map(e => e[key]))];
const arr = deduped.map(el => {
const ndx = array.findIndex(e => e[key] === el);
return {
[key]: array[ndx][key],
long_id: array.filter(e => e[key] === el).map(e => e.long_id).join(';;')
}
});
return arr;
}
console.log(filterArray(array, 'id'))
As suggested here use an array, hope it helps :)
var array = [{
id: "decafc0ffeefacedbabef00ddeadbeef",
long_id: ["1;;decafc0ffeefacedbabef00ddeadbeef"]
}, {
id: "4bb6ac319db42fabab84826a1c08e8da",
long_id: ["1;;decafc0ffeefacedbabef00ddeadbeef;;47421d5c40b2f15d801ac6ca0ff4e6cd;;4bb6ac319db42fabab84826a1c08e8da"]
}, {
id: "4bb6ac319db42fabab84826a1c08e8da",
long_id: ["1;;decafc0ffeefacedbabef00ddeadbeef;;4ace8bd1ec354275a813d6e3725047c0;;4bb6ac319db42fabab84826a1c08e8da"]
}, {
id: "47421d5c40b2f15d801ac6ca0ff4e6cd",
long_id: ["1;;decafc0ffeefacedbabef00ddeadbeef;;47421d5c40b2f15d801ac6ca0ff4e6cd"]
}, {
id: "4ace8bd1ec354275a813d6e3725047c0",
long_id: ["1;;decafc0ffeefacedbabef00ddeadbeef;;4ace8bd1ec354275a813d6e3725047c0"]
}];
var output = [];
array.forEach(function(item) {
var existing = output.filter(function(v, i) {
return v.id == item.id;
});
if (existing.length) {
var existingIndex = output.indexOf(existing[0]);
output[existingIndex].long_id = output[existingIndex].long_id.concat(item.long_id);
} else {
if (typeof item.value == 'string')
item.long_id = [item.long_id];
output.push(item);
}
});
console.dir(output);

Categories