¿Cómo eliminar el elemento de la matriz en forEach loop?

97

Estoy tratando de eliminar un elemento de una matriz en un forEachbucle, pero tengo problemas con las soluciones estándar que he visto.

Esto es lo que estoy intentando actualmente:

review.forEach(function(p){
   if(p === '\u2022 \u2022 \u2022'){
      console.log('YippeeeE!!!!!!!!!!!!!!!!')
      review.splice(p, 1);
   }
});

Sé que está entrando ifporque lo veo YippeeeeeE!!!!!!!!!!!!!en la consola.

MI PROBLEMA: Sé que mi bucle for y si la lógica son correctos, pero mi intento de eliminar el elemento actual de la matriz está fallando.

ACTUALIZAR:

Probé la respuesta de Xotic750, y el elemento aún no se elimina:

Aquí está la función en mi código:

review.forEach(function (item, index, object) {
    if (item === '\u2022 \u2022 \u2022') {
       console.log('YippeeeE!!!!!!!!!!!!!!!!')
       object.splice(index, 1);
    }
    console.log('[' + item + ']');
});

Aquí está la salida donde la matriz aún no se elimina:

[Scott McNeil]
[reviewed 4 months ago]
[ Mitsubishi is AMAZING!!!]
YippeeeE!!!!!!!!!!!!!!!!
[•  •]

Entonces, obviamente, va a entrar en la declaración if como se indica, pero también es obvio que el [• • •] todavía está allí.

novatoPrgrmr
fuente
8
¿Hay alguna razón por la que estás usando forEach? Si desea eliminar elementos, la función más adecuada es filter.
Jon
2
No si necesita mantener la referencia a la matriz original.
Xotic750
Sí, nos gustaría mantener la referencia a la matriz original.
novicePrgrmr
No está claro a partir de su pregunta, ¿cuál es el problema real que tiene? ¿Puede dar un ejemplo, tal vez un jsFiddle? Parece que quizás debería usar el indexatributo en lugar de itempara susplice
Xotic750
@ Xotic750 Lo siento, aclaración añadida.
novicePrgrmr

Respuestas:

235

¿Parece que estás intentando hacer esto?

Itere y mute una matriz usando Array.prototype.splice

var pre = document.getElementById('out');

function log(result) {
  pre.appendChild(document.createTextNode(result + '\n'));
}

var review = ['a', 'b', 'c', 'b', 'a'];

review.forEach(function(item, index, object) {
  if (item === 'a') {
    object.splice(index, 1);
  }
});

log(review);
<pre id="out"></pre>

Lo que funciona bien para un caso simple en el que no tiene 2 de los mismos valores que los elementos de la matriz adyacente, de lo contrario, tiene este problema.

var pre = document.getElementById('out');

function log(result) {
  pre.appendChild(document.createTextNode(result + '\n'));
}

var review = ['a', 'a', 'b', 'c', 'b', 'a', 'a'];

review.forEach(function(item, index, object) {
  if (item === 'a') {
    object.splice(index, 1);
  }
});

log(review);
<pre id="out"></pre>

Entonces, ¿qué podemos hacer con este problema al iterar y mutar una matriz? Bueno, la solución habitual es trabajar a la inversa. Usando ES3 mientras, pero puede usarlo para azúcar si lo prefiere

var pre = document.getElementById('out');

function log(result) {
  pre.appendChild(document.createTextNode(result + '\n'));
}

var review = ['a' ,'a', 'b', 'c', 'b', 'a', 'a'],
  index = review.length - 1;

while (index >= 0) {
  if (review[index] === 'a') {
    review.splice(index, 1);
  }

  index -= 1;
}

log(review);
<pre id="out"></pre>

Ok, pero querías usar métodos de iteración de ES5. Bueno, una opción sería usar Array.prototype.filter, pero esto no muta la matriz original sino que crea una nueva, por lo que, si bien puede obtener la respuesta correcta, no es lo que parece haber especificado.

También podríamos usar ES5 Array.prototype.reduceRight , no por su propiedad reductora sino por su propiedad de iteración, es decir, iterar en reversa.

var pre = document.getElementById('out');

function log(result) {
  pre.appendChild(document.createTextNode(result + '\n'));
}

var review = ['a', 'a', 'b', 'c', 'b', 'a', 'a'];

review.reduceRight(function(acc, item, index, object) {
  if (item === 'a') {
    object.splice(index, 1);
  }
}, []);

log(review);
<pre id="out"></pre>

O podríamos usar ES5 Array.protoype.indexOf así.

var pre = document.getElementById('out');

function log(result) {
  pre.appendChild(document.createTextNode(result + '\n'));
}

var review = ['a', 'a', 'b', 'c', 'b', 'a', 'a'],
  index = review.indexOf('a');

while (index !== -1) {
  review.splice(index, 1);
  index = review.indexOf('a');
}

log(review);
<pre id="out"></pre>

Pero específicamente desea utilizar ES5 Array.prototype.forEach , entonces, ¿qué podemos hacer? Bueno, necesitamos usar Array.prototype.slice para hacer una copia superficial de la matriz y Array.prototype.reverse para que podamos trabajar a la inversa para mutar la matriz original.

var pre = document.getElementById('out');

function log(result) {
  pre.appendChild(document.createTextNode(result + '\n'));
}

var review = ['a', 'a', 'b', 'c', 'b', 'a', 'a'];

review.slice().reverse().forEach(function(item, index, object) {
  if (item === 'a') {
    review.splice(object.length - 1 - index, 1);
  }
});

log(review);
<pre id="out"></pre>

Por último, ES6 nos ofrece algunas alternativas más, donde no necesitamos hacer copias superficiales y revertirlas. Cabe destacar que podemos utilizar generadores e iteradores . Sin embargo, el apoyo es bastante bajo en la actualidad.

var pre = document.getElementById('out');

function log(result) {
  pre.appendChild(document.createTextNode(result + '\n'));
}

function* reverseKeys(arr) {
  var key = arr.length - 1;

  while (key >= 0) {
    yield key;
    key -= 1;
  }
}

var review = ['a', 'a', 'b', 'c', 'b', 'a', 'a'];

for (var index of reverseKeys(review)) {
  if (review[index] === 'a') {
    review.splice(index, 1);
  }
}

log(review);
<pre id="out"></pre>

Algo a tener en cuenta en todo lo anterior es que, si estuviera quitando NaN de la matriz, comparar con iguales no funcionará porque en Javascript NaN === NaNes falso. Pero vamos a ignorar eso en las soluciones, ya que es otro caso límite no especificado.

Así que ahí lo tenemos, una respuesta más completa con soluciones que todavía tienen casos extremos. El primer ejemplo de código sigue siendo correcto, pero como se indicó, no está exento de problemas.

Xotic750
fuente
Gracias por responder. Intenté usar su solución, pero todavía no está eliminando el elemento de la matriz. Pondré los detalles en la pregunta.
novicePrgrmr
Ponga console.log(review);después de forEach, como en mi ejemplo.
Xotic750
4
Cuidado, esto se rompe si se borran dos elementos consecutivos: var review = ['a', 'a', 'c', 'b', 'a']; producirá ['a', 'c', 'b']
quentinadam
3
NOTA - ¡Esta respuesta es INCORRECTA! El foreach itera a través de la matriz por índice. Una vez que elimina elementos mientras itera, el índice de los siguientes elementos cambia. En este ejemplo, una vez que elimine la primera 'a', el número de índice 1 ahora se convierte en 'c'. Por lo tanto, la primera 'b' ni siquiera se evalúa. Como no intentó eliminarlo, resultó que estaba bien, pero no es así. Debe iterar a través de una copia inversa de la matriz y luego eliminar elementos en la matriz original.
danbars
4
@ Xotic750: la respuesta original (que ahora es el primer fragmento de código) es incorrecta simplemente porque forEach no recorrerá todos los elementos de la matriz, como expliqué en el comentario anterior. Sé que la pregunta era cómo eliminar elementos en un bucle forEach, pero la respuesta simple es que no haces eso. Dado que muchas personas están leyendo esas respuestas y muchas veces copian a ciegas las respuestas (especialmente las respuestas aceptadas), es importante notar fallas en el código. Creo que el bucle while inverso es la solución más simple, más eficiente y más legible y debería ser la respuesta aceptada
danbars
37

Usar en Array.prototype.filterlugar de forEach:

var pre = document.getElementById('out');

function log(result) {
  pre.appendChild(document.createTextNode(result + '\n'));
}

var review = ['a', 'b', 'c', 'b', 'a', 'e'];
review = review.filter(item => item !== 'a');
log(review);
Alemjerus
fuente
10

Aunque la respuesta de Xotic750 proporciona varios puntos buenos y posibles soluciones, a veces lo simple es mejor .

Usted sabe que la matriz en la que se está iterando se está mutando en la iteración en sí (es decir, eliminando un elemento => cambios de índice), por lo que la lógica más simple es retroceder en un lenguaje anticuado for(a la C ):

let arr = ['a', 'a', 'b', 'c', 'b', 'a', 'a'];

for (let i = arr.length - 1; i >= 0; i--) {
  if (arr[i] === 'a') {
    arr.splice(i, 1);
  }
}

document.body.append(arr.join());

Si realmente lo piensas, a forEaches simplemente azúcar sintáctico para un forbucle ... Entonces, si no te está ayudando, simplemente deja de romperte la cabeza contra él.

CPHPython
fuente
1

También puede usar indexOf en su lugar para hacer esto

var i = review.indexOf('\u2022 \u2022 \u2022');
if (i !== -1) review.splice(i,1);
jj689
fuente
1

Entendí que desea eliminar de la matriz usando una condición y tener otra matriz que tenga elementos eliminados de la matriz. ¿Es correcto?

¿Qué tal esto?

var review = ['a', 'b', 'c', 'ab', 'bc'];
var filtered = [];
for(var i=0; i < review.length;) {
  if(review[i].charAt(0) == 'a') {
    filtered.push(review.splice(i,1)[0]);
  }else{
    i++;
  }
}

console.log("review", review);
console.log("filtered", filtered);

Espero que esto ayude...

Por cierto, comparé 'for-loop' con 'forEach'.

Si se elimina en caso de que una cadena contenga 'f', el resultado es diferente.

var review = ["of", "concat", "copyWithin", "entries", "every", "fill", "filter", "find", "findIndex", "flatMap", "flatten", "forEach", "includes", "indexOf", "join", "keys", "lastIndexOf", "map", "pop", "push", "reduce", "reduceRight", "reverse", "shift", "slice", "some", "sort", "splice", "toLocaleString", "toSource", "toString", "unshift", "values"];
var filtered = [];
for(var i=0; i < review.length;) {
  if( review[i].includes('f')) {
    filtered.push(review.splice(i,1)[0]);
  }else {
    i++;
  }
}
console.log("review", review);
console.log("filtered", filtered);
/**
 * review [  "concat",  "copyWithin",  "entries",  "every",  "includes",  "join",  "keys",  "map",  "pop",  "push",  "reduce",  "reduceRight",  "reverse",  "slice",  "some",  "sort",  "splice",  "toLocaleString",  "toSource",  "toString",  "values"] 
 */

console.log("========================================================");
review = ["of", "concat", "copyWithin", "entries", "every", "fill", "filter", "find", "findIndex", "flatMap", "flatten", "forEach", "includes", "indexOf", "join", "keys", "lastIndexOf", "map", "pop", "push", "reduce", "reduceRight", "reverse", "shift", "slice", "some", "sort", "splice", "toLocaleString", "toSource", "toString", "unshift", "values"];
filtered = [];

review.forEach(function(item,i, object) {
  if( item.includes('f')) {
    filtered.push(object.splice(i,1)[0]);
  }
});

console.log("-----------------------------------------");
console.log("review", review);
console.log("filtered", filtered);

/**
 * review [  "concat",  "copyWithin",  "entries",  "every",  "filter",  "findIndex",  "flatten",  "includes",  "join",  "keys",  "map",  "pop",  "push",  "reduce",  "reduceRight",  "reverse",  "slice",  "some",  "sort",  "splice",  "toLocaleString",  "toSource",  "toString",  "values"]
 */

Y eliminar por cada iteración, también un resultado es diferente.

var review = ["of", "concat", "copyWithin", "entries", "every", "fill", "filter", "find", "findIndex", "flatMap", "flatten", "forEach", "includes", "indexOf", "join", "keys", "lastIndexOf", "map", "pop", "push", "reduce", "reduceRight", "reverse", "shift", "slice", "some", "sort", "splice", "toLocaleString", "toSource", "toString", "unshift", "values"];
var filtered = [];
for(var i=0; i < review.length;) {
  filtered.push(review.splice(i,1)[0]);
}
console.log("review", review);
console.log("filtered", filtered);
console.log("========================================================");
review = ["of", "concat", "copyWithin", "entries", "every", "fill", "filter", "find", "findIndex", "flatMap", "flatten", "forEach", "includes", "indexOf", "join", "keys", "lastIndexOf", "map", "pop", "push", "reduce", "reduceRight", "reverse", "shift", "slice", "some", "sort", "splice", "toLocaleString", "toSource", "toString", "unshift", "values"];
filtered = [];

review.forEach(function(item,i, object) {
  filtered.push(object.splice(i,1)[0]);
});

console.log("-----------------------------------------");
console.log("review", review);
console.log("filtered", filtered);

Parque Jun-Hong
fuente
0

¡Lo siguiente le dará todos los elementos que no son iguales a sus caracteres especiales!

review = jQuery.grep( review, function ( value ) {
    return ( value !== '\u2022 \u2022 \u2022' );
} );
Hoja de Jenna
fuente
0

Así es como debe hacerlo:

review.forEach(function(p,index,object){
   if(review[index] === '\u2022 \u2022 \u2022'){
      console.log('YippeeeE!!!!!!!!!!!!!!!!')
      review.splice(index, 1);
   }
});
Alaeddin Hussein
fuente
1
No creo que sea el caso. Cambié mi código asumiendo que p era un índice, y ahora ni siquiera entra en la ifdeclaración.
novicePrgrmr
3
@WhoCares Debería ver la especificación ecma-international.org/ecma-262/5.1/#sec-15.4.4.18 Los argumentos de la función callBack sonitem, index, object
Xotic750