Eliminando elementos con Array.map en JavaScript

90

Me gustaría filtrar una matriz de elementos usando la map()función. Aquí hay un fragmento de código:

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

El problema es que los elementos filtrados todavía usan espacio en la matriz y me gustaría eliminarlos por completo.

¿Alguna idea?

EDITAR: Gracias, me olvidé filter(), lo que quería es en realidad un filter()entonces a map().

EDIT2: Gracias por señalar eso map()y filter()no están implementados en todos los navegadores, aunque mi código específico no estaba destinado a ejecutarse en un navegador.

Vincent Robert
fuente
¿Puede explicar por qué 2 iteraciones son peores que 1? Quiero decir, 2 * O (n) es equivalente a O (2 * n) para mí ...
Vincent Robert

Respuestas:

105

Debería utilizar el filtermétodo en lugar del mapa a menos que desee mutar los elementos de la matriz, además de filtrar.

p.ej.

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

[Editar: por supuesto que siempre puedes sourceArray.filter(...).map(...)filtrar y mutar]

olliej
fuente
3
mapno muta
Gracias
14
Pero puedes mutar en map.
Crazywako
Cuidado con esto: cuando JS pasa la referencia cuando mutas algo con el mapa, cambiará el objeto, pero como está MDN, los mapas devuelven la matriz mutada.
alexOtano
1
La pregunta no preguntaba cómo filtrar, la pregunta preguntaba cómo eliminar en el mapa
Dazzle
1
@alexOtano No, el mapa no muta y no devuelve una matriz mutada. Devuelve una nueva matriz. por ejemplo,x=[1,2,3];y = x.map(z => z*2);console.log(x,y);
Kyle Baker
40

Inspirado por escribir esta respuesta, terminé expandiéndome más tarde y escribiendo una publicación de blog que repasa esto en detalle. Recomiendo verificar eso si desea desarrollar una comprensión más profunda de cómo pensar sobre este problema; trato de explicarlo pieza por pieza y también doy una comparación JSperf al final, repasando las consideraciones de velocidad.

Dicho esto, el tl; dr es esto: para lograr lo que está pidiendo (filtrado y mapeo dentro de una llamada de función), usaríaArray.reduce() .

Sin embargo, el enfoque más legible y (menos importante) generalmente significativamente más rápido 2 es usar el filtro y el mapa encadenados juntos:

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

Lo que sigue es una descripción de cómo Array.reduce()funciona y cómo se puede utilizar para realizar el filtrado y el mapa en una iteración. Nuevamente, si esto está demasiado condensado, recomiendo ver la publicación del blog vinculada arriba, que es una introducción mucho más amigable con ejemplos claros y progresión.


Le da a reduce un argumento que es una función (generalmente anónima).

Esa función anónima toma dos parámetros: uno (como las funciones anónimas pasadas a map / filter / forEach) es el iteratee sobre el que se operará. Hay otro argumento para que la función anónima pasada reduzca, sin embargo, que esas funciones no aceptan, y ese es el valor que se pasará entre llamadas a funciones, a menudo denominado memo .

Tenga en cuenta que mientras Array.filter () toma solo un argumento (una función), Array.reduce () también toma un segundo argumento importante (aunque opcional): un valor inicial para 'memo' que se pasará a esa función anónima como su primer argumento, y posteriormente se puede mutar y pasar entre llamadas a funciones. (Si no se proporciona, entonces 'memo' en la primera llamada de función anónima será por defecto el primer iteratee, y el argumento 'iteratee' será en realidad el segundo valor en la matriz)

En nuestro caso, pasaremos una matriz vacía para comenzar y luego elegiremos si inyectamos nuestro iteratee en nuestra matriz o no según nuestra función; este es el proceso de filtrado.

Finalmente, devolveremos nuestra 'matriz en progreso' en cada llamada de función anónima, y ​​reduce tomará ese valor de retorno y lo pasará como un argumento (llamado memo) a su próxima llamada de función.

Esto permite que el filtro y el mapa sucedan en una iteración, reduciendo nuestro número de iteraciones requeridas a la mitad; sin embargo, solo hacemos el doble de trabajo en cada iteración, por lo que no se guarda nada más que las llamadas a funciones, que no son tan costosas en javascript. .

Para obtener una explicación más completa, consulte los documentos de MDN (o mi publicación mencionada al principio de esta respuesta).

Ejemplo básico de una llamada Reducir:

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

versión más sucinta:

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

Observe que el primer iteratee no era mayor que uno, por lo que se filtró. También tenga en cuenta el InitialMemo, nombrado solo para aclarar su existencia y llamar la atención sobre él. Una vez más, se pasa como 'memo' a la primera llamada de función anónima, y ​​luego el valor devuelto de la función anónima se pasa como el argumento 'memo' a la siguiente función.

Otro ejemplo del caso de uso clásico de memo sería devolver el número más pequeño o más grande en una matriz. Ejemplo:

[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 ejemplo de cómo escribir su propia función de reducción (esto a menudo ayuda a comprender funciones como estas, encuentro):

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

La implementación real permite el acceso a cosas como el índice, por ejemplo, pero espero que esto te ayude a tener una idea sencilla de lo esencial.

Kyle Baker
fuente
2
¡brillante! He querido hacer algo como esto durante años. Decidí intentar descubrir una forma agradable y sorprendente, ¡javascript natural!
jemiloii
Otra utilidad de reducees que, a diferencia de filter+ map, a la devolución de llamada se le puede pasar un argumento de índice que es el índice de la matriz original, y no el de la filtrada.
congusbongus
@KyleBaker El enlace a la publicación de su blog va a una página no encontrada. ¿Puede actualizar el enlace? ¡Gracias!
Tim Philip
10

Eso no es lo que hace el mapa. Realmente quieres Array.filter . O si realmente desea eliminar los elementos de la lista original, tendrá que hacerlo imperativamente con un bucle for.

Patricio
fuente
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
fuente
1
también puedes hacerlovar arr = [1,2,"xxx", "yyy"]; arr = arr.filter(function(e){ return e!="xxx" }) console.log(arr)
jack blank
¿Regresaste 4 años después para agregar texto enorme? menos uno
Gracias.
@ user633183 ¿A quién te refieres? ¿Qué "texto enorme"? Tu comentario no es claro. ¿Estás seguro de que estás comentando sobre el lugar correcto ...?
vsync
2

Sin embargo, debe tener en cuenta que Array.filterno es compatible con todos los navegadores, por lo que debe crear un prototipo:

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

Y al hacerlo, puede crear un prototipo de cualquier método que necesite.

ggasp
fuente
2
Si realmente tiene la intención de realizar un polyfill con este método, utilice un polyfill adecuado o, mejor aún, una biblioteca como Modernizr . De lo contrario, es probable que se encuentre con errores confusos con navegadores oscuros que no se dará cuenta hasta que hayan estado en producción durante demasiado tiempo.
Kyle Baker
0

La siguiente declaración limpia el objeto usando la función de mapa.

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

Acabo de escribir una intersección de matriz que maneja correctamente también los duplicados

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

Primero puede usar el mapa y con el encadenamiento puede usar el 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
fuente