Mappare e filtrare un array contemporaneamente


155

Ho una matrice di oggetti su cui voglio scorrere per produrre una nuova matrice filtrata. Inoltre, ho bisogno di filtrare alcuni degli oggetti dal nuovo array in base a un parametro. Sto provando questo:

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

È un buon approccio? Esiste un metodo migliore? Sono aperto a usare qualsiasi biblioteca come lodash.


che dire di un approccio "object.keys"? developer.mozilla.org/fr/docs/Web/JavaScript/Reference/…
Julo0sS


3
.reduce()è decisamente più veloce rispetto a quello .filter(...).map(...)che ho visto suggerito altrove. Ho creato un test per dimostrare JSPerf stackoverflow.com/a/47877054/2379922
Justin L.

Risposte:


220

Dovresti usare Array.reduceper questo.

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>


In alternativa, il riduttore può essere una funzione pura, come questa

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

2
Per me, il primo argomento, filteredè un oggetto. quindi filtered.pushnon è definito per me.
Rahmathullah M,

4
Ho anche avuto un problema con l' filteredessere un oggetto. Questo perché non stavo passando il "valore iniziale" - l'array vuoto ( []) dopo la funzione di riduzione. es. errato var reduced = options.reduce(function(filtered, option) { ... }); corretto var reduced = options.reduce(function(filtered, option) { ... }, []);
Jon Higgins

Non vi è alcun motivo per reducequesto, è possibile utilizzarlo anche in forEachquanto in ogni iterazione si cambia array filterede quindi non è puro funzionale. Sarebbe un evento più leggibile e compatto.
Marko,

1
@Marko Ho introdotto anche un riduttore puro. PTAL.
thefourtheye

Sì, è puro ora. +1 per i tuoi sforzi. Non che io sia un puro sostenitore della programmazione funzionale, tutt'altro, non riuscivo proprio a capire il punto :) Ma in realtà ho usato il tuo trucco dopo averlo visto, perché è stato molto utile in una determinata situazione. Ma per questa attività potresti voler dare un'occhiata alla funzione flatMap, penso che sia diventata standard dopo aver fatto la tua risposta (anche per questo motivo potrebbe non essere supportata da alcuni browser). Dovrebbe essere più performante dal momento che la concatenazione di matrici come questa rende O (n ^ 2) task fuori da O (n) task.
Marko,

43

Usa riduci, Luke!

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

30

Dal 2019, Array.prototype.flatMap è una buona opzione.

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

Dalla pagina MDN collegata sopra:

flatMappuò essere usato come un modo per aggiungere e rimuovere elementi (modificare il numero di oggetti) durante una mappa. In altre parole, consente di mappare molti elementi su molti elementi (gestendo ciascun elemento di input separatamente), anziché sempre uno a uno. In questo senso, funziona come l'opposto del filtro. Restituisci semplicemente un array a 1 elemento per mantenere l'elemento, un array a più elementi per aggiungere elementi o un array a 0 elementi per rimuovere l'elemento.


5
Ottima soluzione! Tuttavia, i lettori tengono presente che è flatMapstato recentemente introdotto e il supporto del browser è limitato . Potresti voler usare il polyfill / gli spessori ufficiali: flatMap e flat .
Arel,

24

Con ES6 puoi farlo in breve:

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


25
Questo farà due cicli, a seconda di quali filtri di ritorno possono ancora consumare memoria.
hogan,

1
@hogan ma questa è la risposta corretta (potrebbe non essere la soluzione ottimale) alla query originale
vikramvi,

1
@vikramvi grazie per la tua nota. Il fatto è che possiamo ottenere le stesse cose con tonnellate di modi e preferirei il migliore.
hogan,

10

Una linea reducecon la sintassi di diffusione fantasia ES6 è qui!

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);


1
Davvero bello @Maxim! Ho votato questo! Ma ... su ogni elemento aggiunto, deve diffondere tutti gli elementi in result... è come una filter(assigned).map(name)soluzione
0zkr PM

Ben fatto. Mi ci è voluto un secondo per capire cosa stesse succedendo ...assigned ? [name] : []- potrebbe essere più leggibile come...(assigned ? [name] : [])
johansenja

5

Farei un commento, ma non ho la reputazione richiesta. Un piccolo miglioramento alla risposta molto buona di Maxim Kuzmin per renderla più efficiente:

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);

Spiegazione

Invece di distribuire ripetutamente l'intero risultato per ogni iterazione, aggiungiamo solo all'array e solo quando c'è effettivamente un valore da inserire.


3

Usa Array.prototy.filter stesso

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

2

Ad un certo punto, non è più facile (o altrettanto facile) usare a 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>

Tuttavia sarebbe bello se ci fosse una malter()o una fap()funzione che combina le funzioni mape filter. Funzionerebbe come un filtro, tranne che invece di restituire vero o falso, restituirebbe qualsiasi oggetto o un valore nullo / indefinito.


Potresti voler controllare i tuoi meme ... ;-)
Arel,

2

Ho ottimizzato le risposte con i seguenti punti:

  1. Riscrittura if (cond) { stmt; }comecond && stmt;
  2. Utilizzare le funzioni freccia ES6

Presenterò due soluzioni, una che utilizza per ogni , l'altra che utilizza riduci :

Soluzione 1: utilizzare forOach

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);

Soluzione 2: utilizzo di ridurre

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);

Lascio a te decidere quale soluzione scegliere.


1
Perché mai dovresti usare cond && stmt;? Questo è molto più difficile da leggere e non offre alcun vantaggio.
jlh

1

Usando riduci, puoi farlo in una funzione Array.prototype. Ciò recupererà tutti i numeri pari da un array.

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>

Puoi usare lo stesso metodo e generalizzarlo per i tuoi oggetti, in questo modo.

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

arr conterrà ora gli oggetti filtrati.


0

L'uso diretto di .reducepuò essere difficile da leggere, quindi ti consiglio di creare una funzione che generi il riduttore per te:

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

Usalo così:

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'];
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.