Rimozione di elementi con Array.map in JavaScript


97

Vorrei filtrare un array di elementi utilizzando la map()funzione. Ecco uno snippet di codice:

var filteredItems = items.map(function(item)
{
    if( ...some condition... )
    {
        return item;
    }
});

Il problema è che gli elementi filtrati utilizzano ancora lo spazio nell'array e vorrei eliminarli completamente.

Qualche idea?

EDIT: Grazie, mi sono dimenticato filter(), quello che volevo è in realtà un filter()poi a map().

EDIT2: Grazie per averlo segnalato map()e filter()non sono implementati in tutti i browser, sebbene il mio codice specifico non fosse concepito per essere eseguito in un browser.


Puoi spiegare perché 2 iterazioni sono peggiori di 1? Voglio dire, 2 * O (n) è equivalente a O (2 * n) per me ...
Vincent Robert,

Risposte:


109

È necessario utilizzare il filtermetodo anziché eseguire il mapping a meno che non si desideri modificare gli elementi nell'array, oltre al filtro.

per esempio.

var filteredItems = items.filter(function(item)
{
    return ...some condition...;
});

[Modifica: ovviamente potresti sempre fare sourceArray.filter(...).map(...)sia per filtrare che per mutare]


3
mapnon muta
Grazie

16
Ma puoi mutare map.
Crazywako

Attento a questo: poiché JS passa il riferimento quando muti qualcosa con map, cambierà l'oggetto ma allo stato di MDN, maps restituisce l'array mutato.
alexOtano

2
La domanda non chiedeva come filtrare, la domanda chiedeva come eliminare sulla mappa
Dazzle

1
@alexOtano No, la mappa non muta e non restituisce un array mutato. Restituisce un nuovo array. ad esempio,x=[1,2,3];y = x.map(z => z*2);console.log(x,y);
Kyle Baker

43

Ispirato dalla scrittura di questa risposta, ho finito per espandere in seguito e scrivere un post sul blog esaminandolo in dettaglio. Consiglio di verificarlo se vuoi sviluppare una comprensione più profonda di come pensare a questo problema: cerco di spiegarlo pezzo per pezzo e alla fine fornisco anche un confronto JSperf, esaminando le considerazioni sulla velocità.

Detto questo, il tl; dr è questo: per realizzare ciò che stai chiedendo (filtraggio e mappatura all'interno di una chiamata di funzione), dovresti usareArray.reduce() .

Tuttavia, l' approccio più leggibile e (meno importante) di solito significativamente più veloce 2 consiste nell'usare semplicemente il filtro e la mappa concatenati insieme:

[1,2,3].filter(num => num > 2).map(num => num * 2)

Quello che segue è una descrizione di come Array.reduce()funziona e di come può essere utilizzato per eseguire il filtro e la mappatura in un'unica iterazione. Ancora una volta, se questo è troppo condensato, consiglio vivamente di vedere il post del blog collegato sopra, che è un'introduzione molto più amichevole con esempi chiari e progressione.


Si fornisce ridurre un argomento che è una funzione (solitamente anonima).

Quella funzione anonima accetta due parametri: uno (come le funzioni anonime passate a map / filter / forEach) è l'iterato su cui operare. C'è un altro argomento per la funzione anonima passata per ridurre, tuttavia, che quelle funzioni non accettano, e questo è il valore che verrà passato tra le chiamate di funzione, spesso indicato come memo .

Nota che mentre Array.filter () accetta solo un argomento (una funzione), Array.reduce () accetta anche un secondo argomento importante (sebbene opzionale): un valore iniziale per 'memo' che sarà passato a quella funzione anonima come suo primo argomento, e successivamente possono essere modificati e passati tra le chiamate di funzione. (Se non viene fornito, 'memo' nella prima chiamata di funzione anonima sarà per impostazione predefinita il primo iterato e l'argomento 'iteratee' sarà effettivamente il secondo valore nell'array)

Nel nostro caso, passeremo un array vuoto per iniziare, quindi sceglieremo se iniettare il nostro iterato nel nostro array o meno in base alla nostra funzione: questo è il processo di filtraggio.

Infine, restituiremo il nostro "array in corso" su ogni chiamata di funzione anonima e reduce prenderà quel valore di ritorno e lo passerà come argomento (chiamato memo) alla sua successiva chiamata di funzione.

Ciò consente che il filtro e la mappa avvengano in un'unica iterazione, riducendo della metà il numero di iterazioni richieste, ma facendo solo il doppio del lavoro per ogni iterazione, quindi non viene salvato nulla a parte le chiamate di funzione, che non sono così costose in javascript .

Per una spiegazione più completa, fare riferimento ai documenti MDN (o al mio post a cui si fa riferimento all'inizio di questa risposta).

Esempio di base di una chiamata Reduce:

let array = [1,2,3];
const initialMemo = [];

array = array.reduce((memo, iteratee) => {
    // if condition is our filter
    if (iteratee > 1) {
        // what happens inside the filter is the map
        memo.push(iteratee * 2); 
    }

    // this return value will be passed in as the 'memo' argument
    // to the next call of this function, and this function will have
    // every element passed into it at some point.
    return memo; 
}, initialMemo)

console.log(array) // [4,6], equivalent to [(2 * 2), (3 * 2)]

versione più succinta:

[1,2,3].reduce((memo, value) => value > 1 ? memo.concat(value * 2) : memo, [])

Si noti che il primo iterato non era maggiore di uno e quindi è stato filtrato. Notare anche l'iniziale Memo, chiamato solo per rendere chiara la sua esistenza e attirare l'attenzione su di esso. Ancora una volta, viene passato come "memo" alla prima chiamata di funzione anonima, quindi il valore restituito dalla funzione anonima viene passato come argomento "memo" alla funzione successiva.

Un altro esempio del classico caso d'uso per memo sarebbe la restituzione del numero più piccolo o più grande in un array. Esempio:

[7,4,1,99,57,2,1,100].reduce((memo, val) => memo > val ? memo : val)
// ^this would return the largest number in the list.

Un esempio di come scrivere la tua funzione di riduzione (questo spesso aiuta a capire funzioni come queste, trovo):

test_arr = [];

// we accept an anonymous function, and an optional 'initial memo' value.
test_arr.my_reducer = function(reduceFunc, initialMemo) {
    // if we did not pass in a second argument, then our first memo value 
    // will be whatever is in index zero. (Otherwise, it will 
    // be that second argument.)
    const initialMemoIsIndexZero = arguments.length < 2;

    // here we use that logic to set the memo value accordingly.
    let memo = initialMemoIsIndexZero ? this[0] : initialMemo;

    // here we use that same boolean to decide whether the first
    // value we pass in as iteratee is either the first or second
    // element
    const initialIteratee = initialMemoIsIndexZero ? 1 : 0;

    for (var i = initialIteratee; i < this.length; i++) {
        // memo is either the argument passed in above, or the 
        // first item in the list. initialIteratee is either the
        // first item in the list, or the second item in the list.
           memo = reduceFunc(memo, this[i]);
        // or, more technically complete, give access to base array
        // and index to the reducer as well:
        // memo = reduceFunc(memo, this[i], i, this);
    }

    // after we've compressed the array into a single value,
    // we return it.
    return memo;
}

L'implementazione reale consente l'accesso a cose come l'indice, ad esempio, ma spero che questo ti aiuti a ottenere una sensazione semplice per il succo.


2
brillante! Sono anni che volevo fare qualcosa del genere. Ho deciso di provare a escogitare un javascript simpatico, semplice e naturale!
jemiloii

Un'altra utilità di reduceè che, a differenza di filter+ map, al callback può essere passato un argomento index che è l'indice dell'array originale, e non quello di quello filtrato.
congusbongus

@KyleBaker Il collegamento al tuo post sul blog va a una pagina non trovata. Puoi aggiornare il link? Grazie!
Tim Philip,

10

Non è quello che fa la mappa. Vuoi davvero Array.filter . Oppure, se vuoi davvero rimuovere gli elementi dalla lista originale, dovrai farlo imperativamente con un ciclo for.


7

Metodo del filtro array

var arr = [1, 2, 3]

// ES5 syntax
arr = arr.filter(function(item){ return item != 3 })

// ES2015 syntax
arr = arr.filter(item => item != 3)

console.log( arr )


1
puoi anche farevar arr = [1,2,"xxx", "yyy"]; arr = arr.filter(function(e){ return e!="xxx" }) console.log(arr)
jack vuoto

Sei tornato 4 anni dopo per aggiungere un testo enorme? meno uno
Grazie

@ user633183 A chi ti riferisci? quale "testo enorme"? Il tuo commento non è chiaro. Sei sicuro di commentare nel posto giusto ...?
vsync

2

È necessario notare tuttavia che Array.filternon è supportato in tutti i browser, quindi è necessario prototipare:

//This prototype is provided by the Mozilla foundation and
//is distributed under the MIT license.
//http://www.ibiblio.org/pub/Linux/LICENSES/mit.license

if (!Array.prototype.filter)
{
    Array.prototype.filter = function(fun /*, thisp*/)
    {
        var len = this.length;

        if (typeof fun != "function")
            throw new TypeError();

        var res = new Array();
        var thisp = arguments[1];

        for (var i = 0; i < len; i++)
        {
            if (i in this)
            {
                var val = this[i]; // in case fun mutates this

                if (fun.call(thisp, val, i, this))
                   res.push(val);
            }
        }

        return res;
    };
}

E così facendo, puoi prototipare qualsiasi metodo di cui potresti aver bisogno.


2
Se hai davvero intenzione di eseguire il polyfill con questo metodo, utilizza un polyfill appropriato, o meglio ancora una libreria come Modernizr . Altrimenti, probabilmente ti imbatterai in bug confusi con browser oscuri di cui non ti renderai conto fino a quando non saranno stati in produzione per troppo tempo.
Kyle Baker

0

la seguente istruzione pulisce l'oggetto usando la funzione map.

var arraytoclean = [{v:65, toberemoved:"gronf"}, {v:12, toberemoved:null}, {v:4}];
arraytoclean.map((x,i)=>x.toberemoved=undefined);
console.dir(arraytoclean);

0

Ho appena scritto l'intersezione di array che gestisce correttamente anche i duplicati

https://gist.github.com/gkucmierz/8ee04544fa842411f7553ef66ac2fcf0

// array intersection that correctly handles also duplicates

const intersection = (a1, a2) => {
  const cnt = new Map();
  a2.map(el => cnt[el] = el in cnt ? cnt[el] + 1 : 1);
  return a1.filter(el => el in cnt && 0 < cnt[el]--);
};

const l = console.log;
l(intersection('1234'.split``, '3456'.split``)); // [ '3', '4' ]
l(intersection('12344'.split``, '3456'.split``)); // [ '3', '4' ]
l(intersection('1234'.split``, '33456'.split``)); // [ '3', '4' ]
l(intersection('12334'.split``, '33456'.split``)); // [ '3', '3', '4' ]


0

Per prima cosa puoi usare la mappa e con il concatenamento puoi usare il filtro

state.map(item => {
            if(item.id === action.item.id){   
                    return {
                        id : action.item.id,
                        name : item.name,
                        price: item.price,
                        quantity : item.quantity-1
                    }

            }else{
                return item;
            }
        }).filter(item => {
            if(item.quantity <= 0){
                return false;
            }else{
                return true;
            }
        });
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.