Pesquisa de site

programação funcional - Removendo elementos com Array.map em JavaScript


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

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

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

Meu código específico não foi projetado para rodar em um navegador, então não estou preocupado com o suporte específico do navegador para nenhuma das funções.

Alguma ideia?

Resposta #1

Você deve usar o método filter em vez de map, a menos que queira modificar os itens no array, além de filtrá-los.

ex.

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

[Editar: Claro, você sempre pode fazer sourceArray.filter(...).map(...) para filtrar e modificar ao mesmo tempo]

Resposta #2

Inspirado por escrever esta resposta, acabei mais tarde expandindo e escrevendo um post de blog abordando isso em detalhes cuidadosos. Recomendo conferir se você quiser desenvolver uma compreensão mais profunda sobre como pensar sobre este problema — eu tento explicar peça por peça, e também dou uma comparação JSperf no final, abordando considerações de velocidade.

Dito isso, **O resumo é o seguinte:

Para realizar o que você está pedindo (filtrar e mapear em uma única chamada de função), você usaria Array.reduce()**.

No entanto, a abordagem mais legível e (menos importante) geralmente significativamente mais rápida2 é simplesmente usar filter e map 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 ele pode ser usado para realizar filter e map em uma única iteração. Novamente, se isso estiver muito condensado, recomendo fortemente ver o post do blog linkado acima, que é uma introdução muito mais amigável, com exemplos claros e progressão.


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

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

Observe que, enquanto Array.filter() aceita apenas um argumento (uma função), Array.reduce() também aceita um segundo argumento importante (embora opcional): um valor inicial para 'memo' que será passado para essa função anônima como seu primeiro argumento e, subsequentemente, pode ser modificado e passado entre chamadas de função. (Se não for fornecido, então 'memo' na primeira chamada da função anônima será, por padrão, o primeiro iterável, e o argumento 'iteratee' será na verdade o segundo valor no array)

No nosso caso, passaremos um array vazio para começar e, em seguida, escolheremos se iremos injetar nosso iterador em nosso array ou não com base em nossa função -- este é o processo de filtragem.

Finalmente, retornaremos nosso 'array em progresso' em cada chamada de função anônima, e o reduce pegará esse valor de retorno e o passará como argumento (chamado memo) para a próxima chamada de função.

Isso permite que filter e map aconteçam em uma única iteração, reduzindo pela metade o número de iterações necessárias -- apenas fazendo o dobro de trabalho a cada iteração, no entanto, então nada é realmente economizado além das chamadas de função, que não são tão caras no javascript.

Para uma explicação mais completa, consulte os documentos da MDN (ou meu post mencionado 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 iterador não era maior que um, e portanto foi filtrado. Note também o initialMemo, nomeado apenas para deixar sua existência clara e chamar a atenção para ele. Mais uma vez, ele é passado como 'memo' na primeira chamada da função anônima, e então o valor retornado da função anônima é passado como argumento 'memo' para a próxima função.

Outro exemplo do caso de uso clássico para memo seria retornar o menor ou 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 reduce (isso muitas vezes ajuda a entender funções como essa, 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 o acesso a coisas como o índice, por exemplo, mas espero que isso ajude você a ter uma sensação descomplicada do essencial.

Resposta #3

Isso não é o que o map faz. Você realmente quer Array.filter. Ou se você realmente quer remover os elementos da lista original, você vai precisar fazer isso de forma imperativa com um loop for.

Artigos relacionados