How can I skip an array element in .map
?
My code:
var sources = images.map(function (img) {
if(img.src.split(\'.\').pop() === \"json
Here is the piece of code that achieves OP's goal using a single function :
const sources = images.flatMap( (img) =>
(img.split('.').pop() === "json") ? [] : [img]
)
See the developer mozilla documentation of flatMap here
Since 2019, Array.prototype.flatMap is a good option.
images.flatMap(({src}) => src.endsWith('.json') ? [] : src);
From MDN:
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.
I think the most simple way to skip some elements from an array is by using the filter() method.
By using this method (ES5) and the ES6 syntax you can write your code in one line, and this will return what you want:
let images = [{src: 'img.png'}, {src: 'j1.json'}, {src: 'img.png'}, {src: 'j2.json'}];
let sources = images.filter(img => img.src.slice(-4) != 'json').map(img => img.src);
console.log(sources);
Here is a updated version of the code provided by @theprtk. It is a cleaned up a little to show the generalized version whilst having an example.
Note: I'd add this as a comment to his post but I don't have enough reputation yet
/**
* @see http://clojure.com/blog/2012/05/15/anatomy-of-reducer.html
* @description functions that transform reducing functions
*/
const transduce = {
/** a generic map() that can take a reducing() & return another reducing() */
map: changeInput => reducing => (acc, input) =>
reducing(acc, changeInput(input)),
/** a generic filter() that can take a reducing() & return */
filter: predicate => reducing => (acc, input) =>
predicate(input) ? reducing(acc, input) : acc,
/**
* a composing() that can take an infinite # transducers to operate on
* reducing functions to compose a computed accumulator without ever creating
* that intermediate array
*/
compose: (...args) => x => {
const fns = args;
var i = fns.length;
while (i--) x = fns[i].call(this, x);
return x;
},
};
const example = {
data: [{ src: 'file.html' }, { src: 'file.txt' }, { src: 'file.json' }],
/** note: `[1,2,3].reduce(concat, [])` -> `[1,2,3]` */
concat: (acc, input) => acc.concat([input]),
getSrc: x => x.src,
filterJson: x => x.src.split('.').pop() !== 'json',
};
/** step 1: create a reducing() that can be passed into `reduce` */
const reduceFn = example.concat;
/** step 2: transforming your reducing function by mapping */
const mapFn = transduce.map(example.getSrc);
/** step 3: create your filter() that operates on an input */
const filterFn = transduce.filter(example.filterJson);
/** step 4: aggregate your transformations */
const composeFn = transduce.compose(
filterFn,
mapFn,
transduce.map(x => x.toUpperCase() + '!'), // new mapping()
);
/**
* Expected example output
* Note: each is wrapped in `example.data.reduce(x, [])`
* 1: ['file.html', 'file.txt', 'file.json']
* 2: ['file.html', 'file.txt']
* 3: ['FILE.HTML!', 'FILE.TXT!']
*/
const exampleFns = {
transducers: [
mapFn(reduceFn),
filterFn(mapFn(reduceFn)),
composeFn(reduceFn),
],
raw: [
(acc, x) => acc.concat([x.src]),
(acc, x) => acc.concat(x.src.split('.').pop() !== 'json' ? [x.src] : []),
(acc, x) => acc.concat(x.src.split('.').pop() !== 'json' ? [x.src.toUpperCase() + '!'] : []),
],
};
const execExample = (currentValue, index) =>
console.log('Example ' + index, example.data.reduce(currentValue, []));
exampleFns.raw.forEach(execExample);
exampleFns.transducers.forEach(execExample);
You can use after of you method map()
. The method filter()
for example in your case:
var sources = images.map(function (img) {
if(img.src.split('.').pop() === "json"){ // if extension is .json
return null; // skip
}
else {
return img.src;
}
});
The method filter:
const sourceFiltered = sources.filter(item => item)
Then, only the existing items are in the new array sourceFiltered
.
Just .filter()
it first:
var sources = images.filter(function(img) {
if (img.src.split('.').pop() === "json") {
return false; // skip
}
return true;
}).map(function(img) { return img.src; });
If you don't want to do that, which is not unreasonable since it has some cost, you can use the more general .reduce()
. You can generally express .map()
in terms of .reduce
:
someArray.map(function(element) {
return transform(element);
});
can be written as
someArray.reduce(function(result, element) {
result.push(transform(element));
return result;
}, []);
So if you need to skip elements, you can do that easily with .reduce()
:
var sources = images.reduce(function(result, img) {
if (img.src.split('.').pop() !== "json") {
result.push(img.src);
}
return result;
}, []);
In that version, the code in the .filter()
from the first sample is part of the .reduce()
callback. The image source is only pushed onto the result array in the case where the filter operation would have kept it.
update — This question gets a lot of attention, and I'd like to add the following clarifying remark. The purpose of .map()
, as a concept, is to do exactly what "map" means: transform a list of values into another list of values according to certain rules. Just as a paper map of some country would seem weird if a couple of cities were completely missing, a mapping from one list to another only really makes sense when there's a 1 to 1 set of result values.
I'm not saying that it doesn't make sense to create a new list from an old list with some values excluded. I'm just trying to make clear that .map()
has a single simple intention, which is to create a new array of the same length as an old array, only with values formed by a transformation of the old values.