I have an array of objects that I want to iterate over to produce a new filtered array. But also, I need to filter out some of the objects from the new array depending of a parameter. I'm trying this:
function renderOptions(options) {
return options.map(function (option) {
if (!option.assigned) {
return (someNewObject);
}
});
}
Is that a good approach? Is there a better method? I'm open to use any library such as lodash.
This question is related to
javascript
arrays
Since 2019, Array.prototype.flatMap is good option.
options.flatMap(o => o.assigned ? [o.name] : []);
From the MDN page linked above:
flatMap
can be used as a way to add and remove items (modify the number of items) during a map. In other words, it allows you to map many items to many items (by handling each input item separately), rather than always one-to-one. In this sense, it works like the opposite of filter. Simply return a 1-element array to keep the item, a multiple-element array to add items, or a 0-element array to remove the item.
One line reduce
with ES6 fancy spread syntax is here!
var options = [_x000D_
{ name: 'One', assigned: true }, _x000D_
{ name: 'Two', assigned: false }, _x000D_
{ name: 'Three', assigned: true }, _x000D_
];_x000D_
_x000D_
const filtered = options_x000D_
.reduce((result, {name, assigned}) => [...result, ...assigned ? [name] : []], []);_x000D_
_x000D_
console.log(filtered);
_x000D_
With ES6 you can do it very short:
options.filter(opt => !opt.assigned).map(opt => someNewObject)
At some point, isn't it easier(or just as easy) to use a forEach
var options = [_x000D_
{ name: 'One', assigned: true }, _x000D_
{ name: 'Two', assigned: false }, _x000D_
{ name: 'Three', assigned: true }, _x000D_
];_x000D_
_x000D_
var reduced = []_x000D_
options.forEach(function(option) {_x000D_
if (option.assigned) {_x000D_
var someNewValue = { name: option.name, newProperty: 'Foo' }_x000D_
reduced.push(someNewValue);_x000D_
}_x000D_
});_x000D_
_x000D_
document.getElementById('output').innerHTML = JSON.stringify(reduced);
_x000D_
<h1>Only assigned options</h1>_x000D_
<pre id="output"> </pre>
_x000D_
However it would be nice if there was a malter()
or fap()
function that combines the map
and filter
functions. It would work like a filter, except instead of returning true or false, it would return any object or a null/undefined.
Use reduce, Luke!
function renderOptions(options) {
return options.reduce(function (res, option) {
if (!option.assigned) {
res.push(someNewObject);
}
return res;
}, []);
}
Use Array.prototy.filter itself
function renderOptions(options) {
return options.filter(function(option){
return !option.assigned;
}).map(function (option) {
return (someNewObject);
});
}
I optimized the answers with the following points:
if (cond) { stmt; }
as cond && stmt;
I'll present two solutions, one using forEach, the other using reduce:
Solution 1: Using forEach
var options = [_x000D_
{ name: 'One', assigned: true }, _x000D_
{ name: 'Two', assigned: false }, _x000D_
{ name: 'Three', assigned: true }, _x000D_
];_x000D_
var reduced = []_x000D_
options.forEach(o => {_x000D_
o.assigned && reduced.push( { name: o.name, newProperty: 'Foo' } );_x000D_
} );_x000D_
console.log(reduced);
_x000D_
Solution 2: Using reduce
var options = [_x000D_
{ name: 'One', assigned: true }, _x000D_
{ name: 'Two', assigned: false }, _x000D_
{ name: 'Three', assigned: true }, _x000D_
];_x000D_
var reduced = options.reduce((a, o) => {_x000D_
o.assigned && a.push( { name: o.name, newProperty: 'Foo' } );_x000D_
return a;_x000D_
}, [ ] );_x000D_
console.log(reduced);
_x000D_
I leave it up to you to decide which solution to go for.
Direct use of .reduce
can be hard to read, so I'd recommend creating a function that generates the reducer for you:
function mapfilter(mapper) {
return (acc, val) => {
const mapped = mapper(val);
if (mapped !== false)
acc.push(mapped);
return acc;
};
}
Use it like so:
const words = "Map and filter an array #javascript #arrays";
const tags = words.split(' ')
.reduce(mapfilter(word => word.startsWith('#') && word.slice(1)), []);
console.log(tags); // ['javascript', 'arrays'];
Using reduce, you can do this in one Array.prototype function. This will fetch all even numbers from an array.
var arr = [1,2,3,4,5,6,7,8];_x000D_
_x000D_
var brr = arr.reduce((c, n) => {_x000D_
if (n % 2 !== 0) {_x000D_
return c;_x000D_
}_x000D_
c.push(n);_x000D_
return c;_x000D_
}, []);_x000D_
_x000D_
document.getElementById('mypre').innerHTML = brr.toString();
_x000D_
<h1>Get all even numbers</h1>_x000D_
<pre id="mypre"> </pre>
_x000D_
You can use the same method and generalize it for your objects, like this.
var arr = options.reduce(function(c,n){
if(somecondition) {return c;}
c.push(n);
return c;
}, []);
arr
will now contain the filtered objects.
I'd make a comment, but I don't have the required reputation. A small improvement to Maxim Kuzmin's otherwise very good answer to make it more efficient:
const options = [_x000D_
{ name: 'One', assigned: true }, _x000D_
{ name: 'Two', assigned: false }, _x000D_
{ name: 'Three', assigned: true }, _x000D_
];_x000D_
_x000D_
const filtered = options_x000D_
.reduce((result, { name, assigned }) => assigned ? result.concat(name) : result, []);_x000D_
_x000D_
console.log(filtered);
_x000D_
Explanation
Instead of spreading the entire result over and over for each iteration, we only append to the array, and only when there's actually a value to insert.
Source: Stackoverflow.com