JS: Don't add value to map on condition - javascript

I have an object that I need to convert into an array. This is the object:
const dogCounts: {
maltese: 4,
poodle: 2,
labrador: 10,
corso: 0
}
And I send it to a component thru props and I have a useMemo hook to convert it into a structure like this: [ [maltese, 4], [poodle, 2], ... ]
const formatDogCounts = useMemo(() => {
const z = Object.keys(dogCounts || {})?.map(i => {
if (dogCounts[i] === 0) return; // Don't add it to map and skip it
return [i, dogCounts[i]]
})
}, [dogCounts])
When the number is zero I don't want to add it to the formatDogCounts variable. What I put above doesn't fit to eslints rules. Arrow function expected no return value.eslintconsistent-return.
Also I put that {} in the object.keys for the case when the counts haven't loaded yet is there a cleaner way to null check that?

map doesn't filter out values; doing a simple return; in map makes the corresponding entry in the result array undefined. If you want to do that, you'll need to filter first, or build the array another way (such as a simple loop).
Here's the filter approach:
const formatDogCounts = useMemo(() => {
const z = Object.keys(dogCounts || {})?
.filter(name => dogCounts[name] !== 0)
.map(name => [name, dogCounts[i]]);
}, [dogCounts]);
Note that Object.entries provides the very [name, value] pairs you want, so you could avoid map, and there's no reason for the conditional chaining operator as neither Object.keys nor Object.entries ever returns undefined or null:
const formatDogCounts = useMemo(() => {
const z = Object.entries(dogCounts || {})
.filter(([, value]) => value !== 0);
}, [dogCounts]);
Note the , prior to value in the [, value] destructuring pattern so we're grabbing the second array entry (the value), not the first (the name).
We can also avoid the calls to Object.entries and filter entirely when there is no dogCounts:
const formatDogCounts = useMemo(() => {
const z = dogCounts
? Object.entries(dogCounts).filter(([, value]) => value !== 0)
: [];
}, [dogCounts]);
In a comment you've said:
The entries solution worked really well for me! Is there a way now to return an object instead of an array with total dog counts and then an array of items? Ex: formatDogCounts: { totalDogs: 30, items: [...] }
Sure. If there will only ever be a reasonable number of dogs (fewer than hundreds of thousands), I'd just do it as a separate operation at the end:
const formatDogCounts = useMemo(() => {
const items = dogCounts
? Object.entries(dogCounts).filter(([, value]) => value !== 0)
: [];
return {
totalDogs: items.reduce((sum, [, value]) => sum + value, 0),
items,
};
}, [dogCounts]);
(A straight sum is the only ad hoc use of reduce I'll do, and even then I don't much care for it.)
Or you could make your filter callback slightly impure and count them as you go:
const formatDogCounts = useMemo(() => {
let totalDogs = 0;
const items = dogCounts
? Object.entries(dogCounts).filter(([, value]) => {
totalDogs += value;
return value !== 0;
})
: [];
return {
totalDogs,
items,
};
}, [dogCounts]);

If you want to perform a map and filter operation together, you can use flatMap, returning an empty array to skip an element.
const formatDogCounts = useMemo(() => {
const z = Object.keys(dogCounts || {})?.flatMap(i => {
if(dogCounts[i] === 0) return []; // Dont add it to map and skip it
return [[i, dogCounts[i]]];
})
}, [dogCounts])

You are trying to filter using a map which isn't possible. Map will return the same amount of values as you put in. You can do a foreach or you can do a combination of map and filter to get the expected results.
Foreach
const z = []
Object.keys(dogCounts).forEach((key) => {
if(dogCounts[key]) {
// if the value is truthy push the structure to the array.
z.push([key, dogCounts[key]]);
}
}
Map/Filter
const z = Object.keys(dogCount)
.map((key) => [key, dogCount[key]) // map to restructure object.keys
.filter(([key, value]) => value); // filter to remove falsey values (0, null, undefined)

Related

Dynamic keys and properties in object [duplicate]

Suppose I have an array of object:
const apple = [{"bookName" :'Harry Pottar',part:"1"},{"bookName" :'Harry Pottar',part:"2"},
{"bookName": 'LOTR',part:"1"},{"bookName": 'LOTR',part:"2"},{"bookName": 'LOTR',part:"3"}]
I want to get count of all common values along with the value name as :
Expected O/P : [{"Harry Pottar":2},{"LOTR":3"}]
For this I tried as:
const id = "Harry Pottar";
const count = array.reduce((acc, cur) => cur.bookName === id ? ++acc : acc, 0);
As this gives the count, by this I can get count for each bookName. But how can I achieve my expected O/P scenario.
If anyone needs any further information please do let me know.
Good to see you know about .reduce! You’re pretty close, just need to save the result to a hashmap (plain object in JS).
const array = [{"bookName" :'Harry Pottar',part:"1"},{"bookName" :'Harry Pottar',part:"2"},{"bookName": 'LOTR',part:"1"},{"bookName": 'LOTR',part:"2"},{"bookName": 'LOTR',part:"3"}]
const result = array.reduce((acc, item) => {
const key = item.bookName
if (!acc.hasOwnProperty(key)) {
acc[key] = 0
}
acc[key] += 1
return acc
}, {})
// not sure why you want the result to be multiple objects. But here you go:
const output = Object.entries(result).map(([key, value])=> ({ [key]: value }))
Create a map from your data keyed by the book names, where the corresponding values are the objects you want in the output, with the count set to zero (you can use the computed property name syntax for the object's dynamic property). Then iterate the data again to increment the counters. Finally extract the values from the map into an array:
const apple = [{"bookName" :'Harry Pottar',part:"1"},{"bookName" :'Harry Pottar',part:"2"},
{"bookName": 'LOTR',part:"1"},{"bookName": 'LOTR',part:"2"},{"bookName": 'LOTR',part:"3"}];
let map = new Map(apple.map(({bookName}) => [bookName, { [bookName]: 0 }]));
for (let {bookName} of apple) map.get(bookName)[bookName]++;
let result = Array.from(map.values());
console.log(result);
You were pretty close. You don't necessarily need to have those objects in an array though. Just have an object with the booknames as the property keys. It would make it easier to manage.
If you then want to create an array of objects from that data you can use map over the Object.entries of that object.
const apple = [{"bookName" :'Harry Pottar',part:"1"},{"bookName" :'Harry Pottar',part:"2"},{"bookName": 'LOTR',part:"1"},{"bookName": 'LOTR',part:"2"},{"bookName": 'LOTR',part:"3"}];
const out = apple.reduce((acc, { bookName }) => {
// If the property doesn't exist, create it
// and set it to zero, otherwise increment the value
// of the existing property
acc[bookName] = (acc[bookName] || 0) + 1;
return acc;
}, {});
console.log(out);
const result = Object.entries(out).map(([ key, value ]) => {
return { [key]: value };
});
console.log(result);

ES6 Filtering objects by unique attribute

I've an array of errors, each error has a non-unique param attribute.
I'd like to filter the array based on whether the param has been seen before.
Something like this:
const filteredErrors = [];
let params = [];
for(let x = 0; x < errors.length; x++) {
if(!params.includes(errors[x].param)) {
params.push(errors[x].param);
filteredErrors.push(errors[x]);
}
}
But I've no idea how to do this in ES6.
I can get the unique params const filteredParams = Array.from(new Set(errors.map(error => error.param)));
but not the objects themselves.
Pretty sure this is just a weakness in my understanding of higher order functions, but I just can't grasp it
You could destrucure param, check against params and add the value to params and return true for getting the object as filtering result.
As result you get an array of first found errors of the same type.
const
params = [],
filteredErrors = errors.filter(({ param }) =>
!params.includes(param) && params.push(param));
Instead of an array you can make use of an object to keep a map of existing values and make use of filter function
let params = {};
const filteredErrors = errors.filter(error => {
if(params[error.param]) return false;
params[error.param] = true;
return true;
});
i'd probably do it like this with a reduce and no need for outside parameters:
const filteredErrors = Object.values(
errors.reduce((acc, val) => {
if (!acc[val.param]) {
acc[val.param] = val;
}
return acc;
}, {}))
basically convert it into an object keyed by the param with the object as values, only setting the key if it hasn't been set before, then back into an array of the values.
generalized like so
function uniqueBy(array, prop) {
return Object.values(
array.reduce((acc, val) => {
if (!acc[val[prop]]) {
acc[val[prop]] = val;
}
return acc;
}, {}))
}
then just do:
const filteredErrors = uniqueBy(errors, 'param');
If your param has a flag identifier if this param has been seen before then you can simply do this.
const filteredErrors = errors.filter(({ param }) => param.seen === true);
OR
const filteredErrors = errors.filter((error) => error.param.seen);
errors should be an array of objects.
where param is one of the fields of the element of array errors and seen is one of the fields of param object.
You can do it by using Array.prototype.reduce. You need to iterate through the objects in the array and keep the found params in a Set if it is not already there.
The Set.prototype.has will let you find that out. If it is not present in the Set you add it both in the Set instance and the final accumulated array, so that in the next iteration if the param is present in your Set you don't include that object:
const errors = [{param: 1, val: "err1"}, {param: 2, val: "err2"}, {param: 3, val: "err3"}, {param: 2, val: "err4"}, {param: 1, val: "err5"}];
const { filteredParams } = errors.reduce((acc, e) => {
!acc.foundParams.has(e.param) && (acc.foundParams.add(e.param) &&
acc.filteredParams.push(e));
return acc;
}, {foundParams: new Set(), filteredParams: []});
console.log(filteredParams);

Create an array from an object with with key and its suffix

Looking for help to convert/group an object to an array using a key, the key has a difference with its (-)suffix.
const obj = {
"name-1":"a",
"age-1":"20",
"email-1":"a#email.com",
"name-2":"b",
"age-2":"24",
"email-2":"b#email.com",
"name-3":"c",
"age-3":"22",
"email-3":"c#email1.com"
};
Expected result
[
{
"name":"a",
"age":"20",
"email":"a#email.com"
},
{
"name":"b",
"age":"24",
"email":"b#email.com"
},
{
"name":"c",
"age":"22",
"email":"c#email.com"
}
]
Maybe due wrong search keyword unable to find a duplicate question.
You can run a reduce on Object.entries and split the keys say name-1, age-2 etc by '-' and return an array of objects.
const obj = {
"name-1":"a",
"age-1":"20",
"email-1":"a#email.com",
"name-2":"b",
"age-2":"24",
"email-2":"b#email.com",
"name-3":"c",
"age-3":"22",
"email-3":"c#email1.com"
};
const res = Object.entries(obj).reduce((acc, [key, value]) => {
const [ k, i ] = key.split('-');
acc[i - 1] = acc[i - 1] || {};
acc[i-1][k] = value;
return acc;
}, [])
console.log(res);
At the code above inside the reduce I split the key by '-' and it gives me a key of an object and the index of the final array.
Then I check if the index i - 1 exists in the array. If not then initialized it by an empty object. Here I use i - 1 because the given object keys are starting from 1 but an array starts from 0.
Finally, I put the object value into the newly created object.
This is a nice algorithm question that I will resolve by writing it with ES6 syntax.
You can achieve this thanks to some functions such as Object.entries and reduce
Example:
const obj = {
"name-1":"a",
"age-1":"20",
"email-1":"a#email.com",
"name-2":"b",
"age-2":"24",
"email-2":"b#email.com",
"name-3":"c",
"age-3":"22",
"email-3":"c#email1.com"
};
const result = Object.entries(obj)
// Here we destructure the entry with on the left the key, and value on the right
.reduce((accumulator, [key, value]) => {
const [property, index] = key.split('-');
// Get the value currently being filled, or an empty object if it doesn't
// exist yet.
const entry = accumulator[index] || {};
accumulator[index] = {
// Spread the current entry to which we are adding
// the property to the object being filled
...entry,
// Dynamic key syntax
[property]: value,
};
return accumulator;
}, [])
// Remove "holes" from the array since it's indexed with given keys
.filter(value => value !== undefined);
console.log(result);

Replace map() with reduce() function

I try to replace map to reduce but without success. Can you help me rewrite this piese of code:
this.tares = this.tares
.map(tare => {
let suppliers = [];
this.organizations.forEach(organization => {
if (organization.tare_ids.indexOf(tare.id) !== -1) {
suppliers = suppliers.concat(tare.suppliers);
}
});
return { ...tare, suppliers };
});
My supplier variable as an accumulator parameter of a reduce function but in this case, I can't figure out how to apply reduce. Nested arrays crashing me.
You could take a Set for all id and map the array with an object and a check if id exists.
var ids = this.organizations.reduce(
(s, { tare_ids }) => tare_ids.reduce((t, id) => t.add(id), s),
new Set
);
this.tares = this.tares.map(tare => ({ ...tare, suppliers: ids.has(tare.id) ? tare.suppliers : [] }));

using reduce to remove item from array

I need a bit of help to remove items from an array. I have a number of check boxes, each have a dynamically created data attribute. On un-checking a check box I want to remove items from an array which match item.sector value of each array item. I can't quite get it working. Any help would be appreciated.
let mapMarkers = [];
function addFilter(self) {
const markerObject = filterObject[self.id];
const markerID = markerObject[0]["id"];
const dataSetSector = self.dataset.sector;
const dataSetYear = self.dataset.year;
if (self.checked) {
mapMarkers.push(markerObject);
} else {
// data attribute SECTOR exists
if (self.hasAttribute("data-sector")) {
mapMarkers = mapMarkers.reduce((acc, curr) => {
if (curr.sector !== dataSetSector) acc.push(curr);
return acc;
});
}
// data attribute YEAR exists
else if (self.hasAttribute("data-year")) {
mapMarkers = mapMarkers.reduce((acc, curr) => {
if (curr.sector !== dataSetYear) acc.push(curr);
return acc;
});
}
}
}
The second argument of reduce is the initial value, i.e. the initial value of acc. Since you're calling a push method on acc, it should probably be an array.
When you omit the second argument, the first element in your array is used as the initial value. I.e.: the first call takes (mapMarkers[0], mapMarkers[1]).
To fix this issue, pass a new array to the reduce method:
mapMarkers = mapMarkers.reduce((acc, curr) => {
if (curr.sector !== dataSetSector) acc.push(curr);
return acc;
}, []);
// ^--- change
Alternatively, as suggested in the comments, you can use filter.

Categories