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"]
210
demandé sur Xufox 2014-07-17 18:48:16

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é.

314
répondu Pointy 2018-08-24 13:20:35

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!!!']
15
répondu theptrk 2017-11-11 22:02:00

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]
9
répondu mpen 2017-02-13 00:12:30

Réponse sans cas de bord superflu:

const thingsWithoutNulls = things.reduce((acc, thing) => {
  if (thing !== null) {
    acc.push(thing);
  }
  return acc;
}, [])
6
répondu corysimmons 2018-02-19 14:52:02

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"]
3
répondu simhumileco 2018-08-10 21:55:04

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"]
2
répondu DJDaveMark 2017-07-26 12:27:30

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'];
2
répondu Alex 2018-06-27 09:34:17