Mapper et filtrer un tableau en même temps

155

J'ai un tableau d'objets que je veux parcourir pour produire un nouveau tableau filtré. Mais aussi, je dois filtrer certains des objets du nouveau tableau en fonction d'un paramètre. J'essaye ceci:

function renderOptions(options) {
    return options.map(function (option) {
        if (!option.assigned) {
            return (someNewObject);
        }
    });   
}

Est-ce une bonne approche? Existe-t-il une meilleure méthode? Je suis ouvert à utiliser n'importe quelle bibliothèque telle que lodash.

Daniel Calderon Mori
la source
qu'en est-il d'une approche "object.keys"? developer.mozilla.org/fr/docs/Web/JavaScript/Reference/…
Julo0sS
3
.reduce()est définitivement plus rapide que de faire .filter(...).map(...)ce que j'ai vu suggéré ailleurs. J'ai mis en place un test JSPerf pour démontrer stackoverflow.com/a/47877054/2379922
Justin L.

Réponses:

220

Vous devriez utiliser Array.reducepour cela.

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

var reduced = options.reduce(function(filtered, option) {
  if (option.assigned) {
     var someNewValue = { name: option.name, newProperty: 'Foo' }
     filtered.push(someNewValue);
  }
  return filtered;
}, []);

document.getElementById('output').innerHTML = JSON.stringify(reduced);
<h1>Only assigned options</h1>
<pre id="output"> </pre>


Alternativement, le réducteur peut être une fonction pure, comme ceci

var reduced = options.reduce(function(result, option) {
  if (option.assigned) {
    return result.concat({
      name: option.name,
      newProperty: 'Foo'
    });
  }
  return result;
}, []);
thefourtheye
la source
2
Pour moi, le premier argument, filteredc'est un objet. c'est donc filtered.pushindéfini pour moi.
Rahmathullah M
4
J'ai également eu un problème avec le fait d' filteredêtre un objet. C'était parce que je ne passais pas la "valeur initiale" - le tableau vide ( []) après la fonction de réduction. par exemple incorrect var reduced = options.reduce(function(filtered, option) { ... }); correct var reduced = options.reduce(function(filtered, option) { ... }, []);
Jon Higgins
Il n'y a aucune raison pour cela reduce, vous pouvez tout aussi bien l'utiliser forEachcar à chaque itération vous changez de tableau filteredet donc ce n'est pas purement fonctionnel. Ce serait un événement plus lisible et compact.
Marko
1
@Marko J'ai également introduit un réducteur pur. PTAL.
thefourtheye
Yap c'est pur maintenant. +1 pour votre effort. Non pas que je sois un pur partisan de la programmation fonctionnelle, loin de là, je ne pouvais tout simplement pas voir le point :) Mais en fait, j'ai utilisé votre astuce après l'avoir vue, car elle était si utile dans la situation donnée. Mais pour cette tâche, vous voudrez peut-être jeter un coup d'œil à la fonction flatMap, je pense qu'elle est entrée dans la norme après que vous ayez fait votre réponse (aussi pour cette raison peut être non prise en charge par certains navigateurs). Il devrait être plus performant car la concaténation de tableaux comme celui-ci rend O (n ^ 2) tâche hors de la tâche O (n).
Marko
43

Utilisez réduire, Luke!

function renderOptions(options) {
    return options.reduce(function (res, option) {
        if (!option.assigned) {
            res.push(someNewObject);
        }
        return res;
    }, []);   
}
Zuker
la source
30

Depuis 2019, Array.prototype.flatMap est une bonne option.

options.flatMap(o => o.assigned ? [o.name] : []);

À partir de la page MDN liée ci-dessus:

flatMappeut être utilisé pour ajouter et supprimer des éléments (modifier le nombre d'éléments) pendant une carte. En d'autres termes, il vous permet de mapper de nombreux éléments à de nombreux éléments (en gérant chaque élément d'entrée séparément), plutôt que toujours un à un. En ce sens, cela fonctionne comme l'opposé du filtre. Renvoyez simplement un tableau à 1 élément pour conserver l'élément, un tableau à plusieurs éléments pour ajouter des éléments ou un tableau à 0 élément pour supprimer l'élément.

Trevor Dixon
la source
5
Excellente solution! Mais, les lecteurs gardent à l'esprit que cela a flatMapété récemment introduit et que la prise en charge de son navigateur est limitée . Vous voudrez peut-être utiliser le polyfill / shims officiel: flatMap et flat .
Arel
24

Avec ES6, vous pouvez le faire très brièvement:

options.filter(opt => !opt.assigned).map(opt => someNewObject)

Natividad Lara Diaz
la source
25
Cela fera deux boucles, en fonction de ce que les retours de filtre peuvent encore consommer de la mémoire.
hogan
1
@hogan mais c'est la bonne réponse (peut ne pas être la solution optimale) à la requête d'origine
vikramvi
1
@vikramvi merci pour votre note. Le fait est que nous pouvons réaliser les mêmes choses avec des tonnes de façons et je préférerais la meilleure.
hogan
10

Une ligne reduceavec la syntaxe de propagation fantaisie ES6 est ici!

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

const filtered = options
  .reduce((result, {name, assigned}) => [...result, ...assigned ? [name] : []], []);

console.log(filtered);

Maxim Kuzmin
la source
1
Vraiment sympa @Maxim! Je vote pour ça! Mais ... sur chaque élément ajouté, il doit répartir tous les éléments dans result... est comme une filter(assigned).map(name)solution
0zkr PM
Joli. Il m'a fallu une seconde pour réaliser ce qui se passait ...assigned ? [name] : []- c'est peut-être plus lisible que...(assigned ? [name] : [])
johansenja
5

Je ferais un commentaire, mais je n'ai pas la réputation requise. Une petite amélioration à la très bonne réponse de Maxim Kuzmin pour la rendre plus efficace:

const options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

const filtered = options
  .reduce((result, { name, assigned }) => assigned ? result.concat(name) : result, []);

console.log(filtered);

Explication

Au lieu de répartir le résultat entier encore et encore pour chaque itération, nous ajoutons uniquement au tableau, et uniquement lorsqu'il y a réellement une valeur à insérer.

matsve
la source
3

Utilisez Array.prototy.filter lui-même

function renderOptions(options) {
    return options.filter(function(option){
        return !option.assigned;
    }).map(function (option) {
        return (someNewObject);
    });   
}
AmmarCSE
la source
2

À un moment donné, n'est-il pas plus facile (ou tout aussi facile) d'utiliser un forEach

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

var reduced = []
options.forEach(function(option) {
  if (option.assigned) {
     var someNewValue = { name: option.name, newProperty: 'Foo' }
     reduced.push(someNewValue);
  }
});

document.getElementById('output').innerHTML = JSON.stringify(reduced);
<h1>Only assigned options</h1>
<pre id="output"> </pre>

Cependant, ce serait bien s'il y avait une fonction malter()ou fap()qui combine les fonctions mapet filter. Cela fonctionnerait comme un filtre, sauf qu'au lieu de renvoyer vrai ou faux, il renverrait n'importe quel objet ou un nul / non défini.

Daniel
la source
Vous voudrez peut-être vérifier vos mèmes ... ;-)
Arel
2

J'ai optimisé les réponses avec les points suivants:

  1. Réécrire if (cond) { stmt; }commecond && stmt;
  2. Utiliser les fonctions fléchées ES6

Je vais présenter deux solutions, l'une utilisant forEach , l'autre utilisant réduire :

Solution 1: utilisation de forEach

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];
var reduced = []
options.forEach(o => {
  o.assigned && reduced.push( { name: o.name, newProperty: 'Foo' } );
} );
console.log(reduced);

Solution 2: Utilisation de réduire

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];
var reduced = options.reduce((a, o) => {
  o.assigned && a.push( { name: o.name, newProperty: 'Foo' } );
  return a;
}, [ ] );
console.log(reduced);

Je vous laisse le soin de décider de la solution à adopter.

Stephen Quan
la source
1
Pourquoi diable utiliseriez-vous cond && stmt;? Ceci est beaucoup plus difficile à lire et n'offre aucun avantage.
jlh
2

En utilisant la réduction, vous pouvez le faire dans une fonction Array.prototype. Cela récupérera tous les nombres pairs d'un tableau.

var arr = [1,2,3,4,5,6,7,8];

var brr = arr.reduce((c, n) => {
  if (n % 2 !== 0) {
    return c;
  }
  c.push(n);
  return c;
}, []);

document.getElementById('mypre').innerHTML = brr.toString();
<h1>Get all even numbers</h1>
<pre id="mypre"> </pre>

Vous pouvez utiliser la même méthode et la généraliser pour vos objets, comme ceci.

var arr = options.reduce(function(c,n){
  if(somecondition) {return c;}
  c.push(n);
  return c;
}, []);

arr contiendra désormais les objets filtrés.

Bhargav Ponnapalli
la source
0

L'utilisation directe de .reducepeut être difficile à lire, je vous recommande donc de créer une fonction qui génère le réducteur pour vous:

function mapfilter(mapper) {
  return (acc, val) => {
    const mapped = mapper(val);
    if (mapped !== false)
      acc.push(mapped);
    return acc;
  };
}

Utilisez-le comme ceci:

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'];
Quelklef
la source