Comment sauter un élément dans.map()?
Comment puis-je ignorer un élément de tableau dans .map
?
Mon code:
var sources = images.map(function (img) {
if(img.src.split('.').pop() === "json"){ // if extension is .json
return null; // skip
}
else{
return img.src;
}
});
Cela retournera:
["img.png", null, "img.png"]
7 réponses
Juste .filter()
en premier:
var sources = images.filter(function(img) {
if (img.src.split('.').pop() === "json") {
return false; // skip
}
return true;
}).map(function(img) { return img.src; });
Si vous ne voulez pas faire cela, ce qui n'est pas déraisonnable car cela a un certain coût, vous pouvez utiliser le plus général .reduce()
. Vous pouvez généralement exprimer .map()
en termes de .reduce
:
someArray.map(function(element) {
return transform(element);
});
Peut être écrit comme
someArray.reduce(function(result, element) {
result.push(transform(element));
return result;
}, []);
Donc, si vous devez ignorer les éléments, vous pouvez le faire facilement avec .reduce()
:
var sources = images.reduce(function(result, img) {
if (img.src.split('.').pop() !== "json") {
result.push(img.src);
}
return result;
}, []);
Dans cette version, le code du .filter()
du premier exemple fait partie du rappel .reduce()
. La source d'image est seulement poussée sur le matrice de résultats dans le cas où l'opération de filtrage aurait gardé.
Update: tout ce que je pensais savoir sur cette réponse était faux
Nous ne devrions pas exiger d'augmenter le chaînage de points et d'opérer sur le tableau [].map(fn1).filter(f2)...
puisque cette approche crée des tableaux intermédiaires en mémoire sur chaque fonction reducing
.
La meilleure approche fonctionne sur la fonction de réduction réelle, donc il n'y a qu'un seul passage de données et pas de tableaux supplémentaires.
La fonction de réduction est la fonction passée dans reduce
et prend un accumulateur et une entrée de la source et retourne quelque chose qui ressemble à l'accumulateur
// 1. create a concat reducing function that can be passed into `reduce`
const concat = (acc, input) => acc.concat([input])
// note that [1,2,3].reduce(concat, []) would return [1,2,3]
// transforming your reducing function by mapping
// 2. create a generic mapping function that can take a reducing function and return another reducing function
const mapping = (changeInput) => (reducing) => (acc, input) => reducing(acc, changeInput(input))
// 3. create your map function that operates on an input
const getSrc = (x) => x.src
const mappingSrc = mapping(getSrc)
// 4. now we can use our `mapSrc` function to transform our original function `concat` to get another reducing function
const inputSources = [{src:'one.html'}, {src:'two.txt'}, {src:'three.json'}]
inputSources.reduce(mappingSrc(concat), [])
// -> ['one.html', 'two.txt', 'three.json']
// remember this is really essentially just
// inputSources.reduce((acc, x) => acc.concat([x.src]), [])
// transforming your reducing function by filtering
// 5. create a generic filtering function that can take a reducing function and return another reducing function
const filtering = (predicate) => (reducing) => (acc, input) => (predicate(input) ? reducing(acc, input): acc)
// 6. create your filter function that operate on an input
const filterJsonAndLoad = (img) => {
console.log(img)
if(img.src.split('.').pop() === 'json') {
// game.loadSprite(...);
return false;
} else {
return true;
}
}
const filteringJson = filtering(filterJsonAndLoad)
// 7. notice the type of input and output of these functions
// concat is a reducing function,
// mapSrc transforms and returns a reducing function
// filterJsonAndLoad transforms and returns a reducing function
// these functions that transform reducing functions are "transducers", termed by Rich Hickey
// source: http://clojure.com/blog/2012/05/15/anatomy-of-reducer.html
// we can pass this all into reduce! and without any intermediate arrays
const sources = inputSources.reduce(filteringJson(mappingSrc(concat)), []);
// [ 'one.html', 'two.txt' ]
// ==================================
// 8. BONUS: compose all the functions
// You can decide to create a composing function which takes an infinite number of transducers to
// operate on your reducing function to compose a computed accumulator without ever creating that
// intermediate array
const composeAll = (...args) => (x) => {
const fns = args
var i = fns.length
while (i--) {
x = fns[i].call(this, x);
}
return x
}
const doABunchOfStuff = composeAll(
filtering((x) => x.src.split('.').pop() !== 'json'),
mapping((x) => x.src),
mapping((x) => x.toUpperCase()),
mapping((x) => x + '!!!')
)
const sources2 = inputSources.reduce(doABunchOfStuff(concat), [])
// ['ONE.HTML!!!', 'TWO.TXT!!!']
Voici une solution amusante:
/**
* Filter-map. Like map, but skips undefined values.
*
* @param callback
*/
function fmap(callback) {
return this.reduce((accum, ...args) => {
let x = callback(...args);
if(x !== undefined) {
accum.push(x);
}
return accum;
}, []);
}
Utiliser avec l'opérateur de liaison :
[1,2,-1,3]::fmap(x => x > 0 ? x * 2 : undefined); // [2,4,6]
Réponse sans cas de bord superflu:
const thingsWithoutNulls = things.reduce((acc, thing) => {
if (thing !== null) {
acc.push(thing);
}
return acc;
}, [])
TL; TR;
Je pense que le moyen le plus simple d'ignorer certains éléments du tableau est d'utiliser la méthode filter().
En utilisant cette méthode et ES6 syntaxe, vous pouvez écrire votre code dans une ligne:
var sources = images.filter(img => img.src.slice(-4) != "json").map(img => img.src)
Et cela retournera ce que vous voulez :
["img.png", "img.png"]
Voici une méthode utilitaire qui mappe uniquement les valeurs non nulles (masque l'appel à reduce):
function mapNonNull(arr, cb) {
return arr.reduce(function (accumulator, value, index, arr) {
var result = cb.call(null, value, index, arr);
if (result != null) {
accumulator.push(result);
}
return accumulator;
}, []);
}
var result = mapNonNull(["a", "b", "c"], function (value) {
return value === "b" ? null : value; // exclude "b"
});
console.log(result); // ["a", "c"]
Pourquoi ne pas simplement utiliser une boucle forEach?
let arr = ['a','b','c','d','e'];
let filtered = [];
arr.forEach(x => {
if (!x.includes('b')) filtered.push(x);
});
// filtered === ['a','c','d','e'];