Suppression d'éléments avec Array.map en JavaScript

90

Je voudrais filtrer un tableau d'éléments en utilisant la map()fonction. Voici un extrait de code:

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

Le problème est que les éléments filtrés utilisent toujours de l'espace dans le tableau et je voudrais les effacer complètement.

Une idée?

EDIT: Merci, j'ai oublié filter(), ce que je voulais, c'est en fait un filter()alors un map().

EDIT2: Merci d'avoir indiqué cela map()et filter()ne sont pas implémentés dans tous les navigateurs, bien que mon code spécifique ne soit pas destiné à fonctionner dans un navigateur.

Vincent Robert
la source
Pouvez-vous expliquer pourquoi 2 itérations sont pires que 1? Je veux dire, 2 * O (n) équivaut à O (2 * n) pour moi ...
Vincent Robert

Réponses:

105

Vous devez utiliser la filterméthode plutôt que le mappage, sauf si vous souhaitez muter les éléments du tableau, en plus du filtrage.

par exemple.

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

[Modifier: bien sûr, vous pouvez toujours faire sourceArray.filter(...).map(...)pour filtrer et muter]

olliej
la source
3
mapne mute pas
Merci
14
Mais vous pouvez muter map.
Crazywako
Attention à ceci: lorsque JS passe la référence lorsque vous muter quelque chose avec map, cela changera l'objet, mais comme MDN se tient, maps renvoie le tableau muté.
alexOtano
1
La question ne demandait pas comment filtrer, la question demandait comment supprimer sur la carte
Dazzle
1
@alexOtano Non, la carte ne mute pas et ne renvoie pas de tableau muté. Il renvoie un nouveau tableau. par exemple,x=[1,2,3];y = x.map(z => z*2);console.log(x,y);
Kyle Baker
40

Inspiré par la rédaction de cette réponse, j'ai fini par développer plus tard et écrire un article de blog sur ce sujet en détail. Je vous recommande de vérifier cela si vous souhaitez développer une compréhension plus profonde de la façon de penser à ce problème - j'essaie de l'expliquer pièce par pièce et je donne également une comparaison JSperf à la fin, en passant en revue les considérations de vitesse.

Cela dit, le tl; dr est le suivant: pour accomplir ce que vous demandez (filtrage et mappage dans un seul appel de fonction), vous utiliseriezArray.reduce() .

Cependant, l' approche la plus lisible et (moins important) généralement beaucoup plus rapide 2 consiste simplement à utiliser un filtre et une carte enchaînés ensemble:

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

Ce qui suit est une description de son Array.reduce()fonctionnement et de la manière dont il peut être utilisé pour effectuer un filtre et une carte en une seule itération. Encore une fois, si cela est trop condensé, je recommande vivement de consulter le billet de blog lié ci-dessus, qui est une intro beaucoup plus conviviale avec des exemples clairs et une progression.


Vous donnez à réduire un argument qui est une fonction (généralement anonyme).

Cette fonction anonyme prend deux paramètres - l'un (comme les fonctions anonymes transmises à map / filter / forEach) est l'itéré à utiliser. Il existe un autre argument pour la fonction anonyme transmise pour réduire, cependant, que ces fonctions n'acceptent pas, et c'est la valeur qui sera transmise entre les appels de fonction, souvent appelée mémo .

Notez que si Array.filter () ne prend qu'un seul argument (une fonction), Array.reduce () prend également un deuxième argument important (bien que facultatif): une valeur initiale pour 'memo' qui sera passée dans cette fonction anonyme comme premier argument, et peut ensuite être muté et transmis entre les appels de fonction. (S'il n'est pas fourni, alors 'memo' dans le premier appel de fonction anonyme sera par défaut le premier iteratee, et l'argument 'iteratee' sera en fait la deuxième valeur du tableau)

Dans notre cas, nous passerons un tableau vide pour commencer, puis choisirons d'injecter ou non notre iteratee dans notre tableau en fonction de notre fonction - c'est le processus de filtrage.

Enfin, nous retournerons notre 'tableau en cours' à chaque appel de fonction anonyme, et reduction prendra cette valeur de retour et la transmettra comme argument (appelé mémo) à son prochain appel de fonction.

Cela permet au filtre et à la carte de se produire en une seule itération, réduisant de moitié le nombre d'itérations requises - en effectuant simplement deux fois plus de travail à chaque itération, cependant, rien n'est vraiment enregistré à part les appels de fonction, qui ne sont pas si chers en javascript .

Pour une explication plus complète, reportez-vous à la documentation MDN (ou à mon article référencé au début de cette réponse).

Exemple de base d'un appel de réduction:

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

version plus succincte:

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

Notez que la première itération n'était pas supérieure à un, et a donc été filtrée. Notez également le initialMemo, nommé juste pour rendre son existence claire et attirer l'attention sur lui. Encore une fois, il est passé en tant que «mémo» au premier appel de fonction anonyme, puis la valeur renvoyée de la fonction anonyme est transmise en tant qu'argument «mémo» à la fonction suivante.

Un autre exemple du cas d'utilisation classique pour mémo serait de renvoyer le plus petit ou le plus grand nombre d'un tableau. Exemple:

[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 exemple de la façon d'écrire votre propre fonction de réduction (cela aide souvent à comprendre des fonctions comme celles-ci, je trouve):

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'implémentation réelle permet d'accéder à des éléments comme l'index, par exemple, mais j'espère que cela vous aidera à avoir une idée simple de l'essentiel.

Kyle Baker
la source
2
brillant! Je voulais faire quelque chose comme ça depuis des années. Décidé d'essayer de trouver un joli moyen et wow, javascript naturel!
jemiloii
Une autre utilité de reduceest que, contrairement à filter+ map, le rappel peut recevoir un argument d'index qui est l'index du tableau d'origine, et non celui de celui filtré.
congusbongus
@KyleBaker Le lien vers votre article de blog mène à une page introuvable. Pouvez-vous mettre à jour le lien? Merci!
Tim Philip
10

Ce n'est pas ce que fait la carte. Vous voulez vraiment Array.filter . Ou si vous voulez vraiment supprimer les éléments de la liste d'origine, vous allez devoir le faire impérativement avec une boucle for.

Patrick
la source
6

Array Filter, méthode

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 )

vsync
la source
1
vous pouvez aussi fairevar arr = [1,2,"xxx", "yyy"]; arr = arr.filter(function(e){ return e!="xxx" }) console.log(arr)
jack blank
Vous êtes revenu 4 ans plus tard pour ajouter un gros texte? moins un
Merci
@ user633183 De qui parlez-vous? quel "texte énorme"? Votre commentaire n'est pas clair. Etes-vous sûr de commenter le bon endroit ...?
vsync
2

Vous devez cependant noter que le Array.filtern'est pas pris en charge dans tous les navigateurs, vous devez donc prototyper:

//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;
    };
}

Et ce faisant, vous pouvez prototyper n'importe quelle méthode dont vous pourriez avoir besoin.

ggasp
la source
2
Si vous avez vraiment l'intention de polyfill cette méthode, veuillez utiliser un polyfill approprié, ou mieux encore une bibliothèque comme Modernizr . Sinon, vous rencontrerez probablement des bogues déroutants avec des navigateurs obscurs que vous ne réaliserez pas tant qu'ils n'auront pas été en production depuis trop longtemps.
Kyle Baker
0

L'instruction suivante nettoie l'objet à l'aide de la fonction map.

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

Je viens d'écrire une intersection de tableau qui gère correctement également les doublons

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' ]

gkucmierz
la source
0

Vous pouvez d'abord utiliser la carte et avec le chaînage, vous pouvez utiliser le filtre

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;
            }
        });
Rishab
la source