Removendo elementos com Array.map em JavaScript

90

Eu gostaria de filtrar uma matriz de itens usando a map()função. Aqui está um snippet de código:

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

O problema é que os itens filtrados ainda usam espaço na matriz e eu gostaria de eliminá-los completamente.

Qualquer ideia?

EDIT: Obrigado, esqueci filter(), o que eu queria na verdade é um filter()então a map().

EDIT2: Obrigado por apontar isso map()e filter()não são implementados em todos os navegadores, embora meu código específico não tenha a intenção de ser executado em um navegador.

Vincent Robert
fonte
Você pode explicar por que 2 iterações são piores que 1? Quer dizer, 2 * O (n) é equivalente a O (2 * n) para mim ...
Vincent Robert

Respostas:

105

Você deve usar o filtermétodo em vez de mapear, a menos que queira modificar os itens na matriz, além da filtragem.

por exemplo.

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

[Editar: é claro que você sempre pode fazer sourceArray.filter(...).map(...)para filtrar e modificar]

olliej
fonte
3
mapnão sofre mutação
Obrigado
14
Mas você pode sofrer mutação map.
Crazywako de
Cuidado com isso: como JS passa a referência quando você altera algo com map, ele mudará o objeto, mas como MDN está, maps retorna a matriz mutada.
alexOtano
1
A pergunta não perguntava como filtrar, a pergunta era sobre como excluir no mapa
Dazzle
1
@alexOtano Não, o mapa não sofre mutação e não retorna uma matriz mutada. Ele retorna uma nova matriz. por exemplo,x=[1,2,3];y = x.map(z => z*2);console.log(x,y);
Kyle Baker
40

Inspirado por escrever esta resposta, acabei expandindo mais tarde e escrevendo uma postagem no blog que tratava disso com detalhes cuidadosos. Recomendo verificar isso se você quiser desenvolver um entendimento mais profundo de como pensar sobre esse problema - tento explicá-lo peça por peça e também fornecer uma comparação JSperf no final, passando por considerações de velocidade.

Dito isso, o tl; dr é o seguinte: para realizar o que você está pedindo (filtragem e mapeamento em uma chamada de função), você usariaArray.reduce() .

No entanto, a abordagem mais legível e (menos importante) geralmente significativamente mais rápida 2 é usar apenas o filtro e o mapa encadeados:

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

O que se segue é uma descrição de como Array.reduce()funciona e como pode ser usado para filtrar e mapear em uma iteração. Novamente, se isso estiver muito condensado, eu recomendo fortemente que você veja a postagem do blog no link acima, que é uma introdução muito mais amigável com exemplos claros e progressão.


Você fornece a reduzir um argumento que é uma função (geralmente anônima).

Essa função anônima leva dois parâmetros - um (como as funções anônimas passadas para map / filter / forEach) é o iteratário a ser operado. Há outro argumento para a função anônima passada reduzir, no entanto, que essas funções não aceitam, e é o valor que será passado entre as chamadas de função, geralmente referido como memo .

Observe que enquanto Array.filter () recebe apenas um argumento (uma função), Array.reduce () também assume um segundo argumento importante (embora opcional): um valor inicial para 'memo' que será passado para a função anônima como seu primeiro argumento e, subsequentemente, pode ser alterado e transmitido entre as chamadas de função. (Se não for fornecido, então 'memo' na primeira chamada de função anônima será, por padrão, o primeiro iteratário, e o argumento 'iteratário' será, na verdade, o segundo valor na matriz)

Em nosso caso, passaremos um array vazio para iniciar e, em seguida, escolheremos se injetamos nosso iteratê em nosso array ou não com base em nossa função - este é o processo de filtragem.

Por fim, retornaremos nosso 'array em andamento' em cada chamada de função anônima e reduzir pegará esse valor de retorno e o passará como um argumento (chamado memo) para sua próxima chamada de função.

Isso permite que o filtro e o mapa ocorram em uma iteração, reduzindo nosso número de iterações necessárias pela metade - apenas fazendo o dobro de trabalho em cada iteração, então nada é realmente salvo além de chamadas de função, que não são tão caras em javascript .

Para uma explicação mais completa, consulte os documentos do MDN (ou minha postagem referenciada no início desta resposta).

Exemplo básico de uma chamada 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)]

versão mais sucinta:

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

Observe que o primeiro iteratee não era maior que um e, portanto, foi filtrado. Observe também o initialMemo, nomeado apenas para deixar clara sua existência e chamar a atenção para ele. Mais uma vez, ele é passado como 'memo' para a primeira chamada de função anônima e, em seguida, o valor retornado da função anônima é passado como o argumento 'memo' para a próxima função.

Outro exemplo do caso de uso clássico para memo seria retornar o menor ou o maior número em um array. Exemplo:

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

Um exemplo de como escrever sua própria função de redução (isso geralmente ajuda a entender funções como essas, eu acho):

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

A implementação real permite acesso a coisas como o índice, por exemplo, mas espero que isso ajude você a ter uma noção descomplicada de sua essência.

Kyle Baker
fonte
2
brilhante! Há anos desejo fazer algo assim. Decidi tentar descobrir um bom e jeito e uau, javascript natural!
jemiloii
Outra utilidade de reduceé que, ao contrário de filter+ map, o retorno de chamada pode receber um argumento de índice que é o índice do array original, e não aquele filtrado.
congusbongus
@KyleBaker O link para a postagem do seu blog leva a uma página não encontrada. Você pode atualizar o link? Obrigado!
Tim Philip
10

Não é isso que o mapa faz. Você realmente quer Array.filter . Ou se você realmente deseja remover os elementos da lista original, você precisará fazer isso imperativamente com um loop for.

Patrick
fonte
6

Método de filtro de matriz

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
fonte
1
você também pode fazervar arr = [1,2,"xxx", "yyy"]; arr = arr.filter(function(e){ return e!="xxx" }) console.log(arr)
jack blank
Você voltou 4 anos depois para adicionar um texto enorme? menos um
Obrigado
@ user633183 A quem você está se referindo? que "texto enorme"? Seu comentário não está claro. Tem certeza de que está comentando no lugar certo ...?
vsync
2

Você deve observar, entretanto, que Array.filternão é compatível com todos os navegadores, portanto, você deve prototipar:

//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, fazendo isso, você pode criar um protótipo de qualquer método de que precise.

ggasp
fonte
2
Se você realmente pretende polyfill este método, por favor, use um polyfill adequado, ou melhor ainda, uma biblioteca como a Modernizr . Caso contrário, você provavelmente encontrará bugs confusos com navegadores obscuros que não perceberá até que estejam em produção por muito tempo.
Kyle Baker,
0

a instrução a seguir limpa o objeto usando a função map.

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

Acabei de escrever a intersecção da matriz que lida corretamente com duplicatas

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
fonte
0

Primeiro você pode usar o mapa e com o encadeamento você pode usar o 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;
            }
        });
Rishab
fonte