using reduce to remove item from array - javascript

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.

Related

How to accumulate all values inside a 2D array that share the same field?

I have a 2D array that contains values like this:
var array = [["10/10/2020","1000"],["10/10/2020","300"],["07/10/2020","100"],["07/10/2020","100"],["03/10/2020","100"],["10/10/2020","100"]];
For every nested array that has the same date value (the first element), I want to add up the second value to have something like this:
arrayAdd = [["10/10/2020","1400"],["O7/10/2020","200"],["03/10/2020","100"]]
How can I do this?
var array = [["10/10/2020","1000"],["10/10/2020","300"],["07/10/2020","100"],["07/10/2020","100"],["03/10/2020","100"],["10/10/2020","100"]];
function fromEntries (iterable) {
return [...iterable].reduce((obj, [key, val]) => {
obj[key] = String(obj[key] ? +obj[key] + +val : val)
return obj
}, {})
}
console.log(fromEntries(array))
Step by step:
Create an empty object/map.
Iterate over your array.
Get the first element of each item in the array (date) and check if that key is already in the object.
If it is not, you add it. The value will be the second element (the number).
If it is there already, you increment the value.
Object.entries will turn that object into an array with the shape you want, as you can see below:
const obj = {
"10/10/2020": "1400",
"O7/10/2020": "200",
"03/10/2020": "100",
};
console.log(Object.entries(obj));
.as-console-wrapper {
max-height: 100% !important;
}
This should help you get started.
you can do this
const data = [["10/10/2020","1000"],["10/10/2020","300"],["07/10/2020","100"],["07/10/2020","100"],["03/10/2020","100"],["10/10/2020","100"]];
const result = Object.entries(data.reduce((res, [key, value] ) => {
return {
...res,
[key]: (res[key] || 0) + Number(value)
}
}, {}))
console.log(result)
There are a couple ways to handle this question.
You can either compare the string values of each date to see if they are the same, or you can convert them to date objects and compare them that way. Here's a link to the MDN document that shows how you can get a primitive value for the date.
I'm going to use the string value because, in this case, I'm going to assume that all dates are coming in a consistent format.
let array = [["10/10/2020","1000"],["10/10/2020","300"],["07/10/2020","100"],["07/10/2020","100"],["03/10/2020","100"],["10/10/2020","100"]];
let arrayAdd = []
for (let valuePair of array) {
// grab just the date value
let date = valuePair[0];
// check to see if pair is in arrayAdd
let foundPair = arrayAdd.find(el => el[0] === date)
// will return undefined if nothing found
if (!foundPair) {
// if nothing found, we push the entire value and date pair
arrayAdd.push(valuePair);
} else {
// few things happening here..
// we have to convert each value to number to properly add
// once they're added, we convert that new value back into a string
// and then set the value of the found pair to that new string.
foundPair[1] = (Number(valuePair[1]) +
Number(foundPair[1])).toString();
}
}
console.log(arrayAdd);

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);

Find Unique value from an array based on the array's string value (Javascript)

so I want to find unique values from an array.
so for example I have this array:
const mainArr = ['shape-10983', 'size-2364', 'size-7800', 'size-4602', 'shape-11073', 'size-15027', 'size-15030', 'size-15033', 'height-3399', 'height-5884']
so I want to find the first matching value for each unique item.
for example, in the array, I have two strings with the shape prefix, six items with the size prefix, and two items with the height prefix.
so I want to output to be something like
const requiredVal = ["shape-10983", "size-2364", "height-3399"]
I want only the first value from any set of different values.
the simplest solution will be to iterate on the list and storing what you got in a dictionary
function removeSimilars(input) {
let values = {};
for (let value of input) {//iterate on the array
let key = value.splitOnLast('-')[0];//get the prefix
if (!(key in values))//if we haven't encounter the prefix yet
values[key] = value;//store that the first encounter with the prefix is with 'value'
}
return Object.values(values);//return all the values of the map 'values'
}
a shorter version will be this:
function removeSimilars(input) {
let values = {};
for (let value of input)
values[value.splitOnLast('-')[0]] ??= value;
return Object.values(values);
}
You could split the string and get the type and use it aks key for an object along with the original string as value. At result take only the values from the object.
const
data = ['shape-10983', 'size-2364', 'size-7800', 'size-4602', 'shape-11073', 'size-15027', 'size-15030', 'size-15033', 'height-3399', 'height-5884'],
result = Object.values(data.reduce((r, s) => {
const [type] = s.split('-', 1);
r[type] ??= s;
return r;
}, {}));
console.log(result);
If, as you mentioned in the comments, you have the list of prefixes already available, then all you have to do is iterate over those, to find each first element that starts with that prefix in your full list of possible values:
const prefixes = ['shape', 'size', 'height'];
const list = ['shape-10983', 'size-2364', 'size-7800', 'size-4602', 'shape-11073', 'size-15027', 'size-15030', 'size-15033', 'height-3399', 'height-5884']
function reduceTheOptions(list = [], prefixes = [], uniques = []) {
prefixes.forEach(prefix =>
uniques.push(
list.find(e => e.startsWith(prefix))
)
);
return uniques;
}
console.log(reduceTheOptions(list, prefixes));
Try this:
function getRandomSet(arr, ...prefix)
{
// the final values are load into the array result variable
result = [];
const randomItem = (array) => array[Math.floor(Math.random() * array.length)];
prefix.forEach((pre) => {
result.push(randomItem(arr.filter((par) => String(par).startsWith(pre))));
});
return result;
}
const mainArr = ['shape-10983', 'size-2364', 'size-7800', 'size-4602', 'shape-11073', 'size-15027', 'size-15030', 'size-15033', 'height-3399', 'height-5884'];
console.log("Random values: ", getRandomSet(mainArr, "shape", "size", "height"));
I modified the #ofek 's answer a bit. cuz for some reason the ??= is not working in react project.
function removeSimilars(input) {
let values = {};
for (let value of input)
if (!values[value.split("-")[0]]) {
values[value.split("-")[0]] = value;
}
return Object.values(values);
}
create a new array and loop over the first array and check the existing of element before in each iteration if not push it to the new array

JS: Don't add value to map on condition

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)

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);

Categories