Recorriendo la matriz y eliminando elementos, sin romper el ciclo

462

Tengo lo siguiente para el bucle, y cuando lo uso splice()para eliminar un elemento, obtengo que los 'segundos' no están definidos. Podría comprobar si no está definido, pero creo que probablemente haya una forma más elegante de hacerlo. El deseo es simplemente eliminar un elemento y continuar.

for (i = 0, len = Auction.auctions.length; i < len; i++) {
    auction = Auction.auctions[i];
    Auction.auctions[i]['seconds'] --;
    if (auction.seconds < 0) { 
        Auction.auctions.splice(i, 1);
    }           
}
dzm
fuente
11
Además de iterar hacia atrás y ajustar la longitud, también puede colocar los miembros que desee en una nueva matriz.
RobG
2
¿Por qué dices en Auction.auctions[i]['seconds']--lugar de auction.seconds--?
Don Hatch
probablemente desee ver la función predefinida .shift ();
raku

Respuestas:

856

La matriz se vuelve a indexar cuando realiza un .splice(), lo que significa que omitirá un índice cuando se elimine uno, y su caché .lengthestá obsoleta.

Para solucionarlo, deberías disminuir idespués de a .splice(), o simplemente iterar en reversa ...

var i = Auction.auctions.length
while (i--) {
    ...
    if (...) { 
        Auction.auctions.splice(i, 1);
    } 
}

De esta manera, la reindexación no afecta el siguiente elemento de la iteración, ya que la indexación afecta solo a los elementos desde el punto actual hasta el final de la matriz, y el siguiente elemento de la iteración es inferior al punto actual.

usuario1106925
fuente
151

Este es un problema bastante común. La solución es hacer un bucle hacia atrás:

for (var i = Auction.auctions.length - 1; i >= 0; i--) {
    Auction.auctions[i].seconds--;
    if (Auction.auctions[i].seconds < 0) { 
        Auction.auctions.splice(i, 1);
    }
}

No importa si los está sacando del final porque los índices se conservarán a medida que retrocede.

Frattaro
fuente
48

Vuelva a calcular la longitud cada vez a través del ciclo en lugar de solo al principio, por ejemplo:

for (i = 0; i < Auction.auctions.length; i++) {
      auction = Auction.auctions[i];
      Auction.auctions[i]['seconds'] --;
      if (auction.seconds < 0) { 
          Auction.auctions.splice(i, 1);
          i--; //decrement
      }
}

De esa manera no superarás los límites.

EDITAR: se agregó una disminución en la instrucción if.

Bagazo
fuente
32

Aunque su pregunta es sobre la eliminación de elementos de la matriz que se está iterando y no sobre la eliminación eficiente de elementos (además de algún otro procesamiento), creo que uno debería reconsiderarlo si se encuentra en una situación similar.

La complejidad algorítmica de este enfoque es la O(n^2)función de empalme y el bucle for itera sobre la matriz (la función de empalme desplaza todos los elementos de la matriz en el peor de los casos). En su lugar, puede insertar los elementos necesarios en la nueva matriz y luego asignar esa matriz a la variable deseada (que se repitió).

var newArray = [];
for (var i = 0, len = Auction.auctions.length; i < len; i++) {
    auction = Auction.auctions[i];
    auction.seconds--;
    if (!auction.seconds < 0) { 
        newArray.push(auction);
    }
}
Auction.auctions = newArray;

Desde ES2015 podemos usar Array.prototype.filterpara ajustarlo todo en una línea:

Auction.auctions = Auction.auctions.filter(auction => --auction.seconds >= 0);
0xc0de
fuente
22
Auction.auctions = Auction.auctions.filter(function(el) {
  return --el["seconds"] > 0;
});
Esteta
fuente
10

Si está utilizando ES6 +, ¿por qué no utilizar el método Array.filter?

Auction.auctions = Auction.auctions.filter((auction) => {
  auction['seconds'] --;
  return (auction.seconds > 0)
})  

Tenga en cuenta que modificar el elemento de matriz durante la iteración del filtro solo funciona para objetos y no funcionará para una matriz de valores primitivos.

Rubinsh
fuente
9

Otra solución simple para digerir una matriz de elementos una vez:

while(Auction.auctions.length){
    // From first to last...
    var auction = Auction.auctions.shift();
    // From last to first...
    var auction = Auction.auctions.pop();

    // Do stuff with auction
}
Pablo
fuente
8

Aquí hay otro ejemplo para el uso adecuado del empalme. Este ejemplo está a punto de eliminar 'atributo' de 'matriz'.

for (var i = array.length; i--;) {
    if (array[i] === 'attribute') {
        array.splice(i, 1);
    }
}
daniel.szaniszlo
fuente
8

Para cada persona que ha respondido a esta pregunta muy básica con el código que tiene splice () en un bucle, que ha ejecutado el tiempo O (n 2 ), o que ha votado dicha respuesta, durante los siete años desde que se publicó esta pregunta: debe avergonzarse .

Aquí hay una solución de tiempo lineal simple para este problema de tiempo lineal simple.

Cuando ejecuto este fragmento, con n = 1 millón, cada llamada a filterInPlace () tarda .013 a .016 segundos. Una solución cuadrática (por ejemplo, la respuesta aceptada) tomaría un millón de veces más o menos.

// Remove from array every item such that !condition(item).
function filterInPlace(array, condition) {
   var iOut = 0;
   for (var i = 0; i < array.length; i++)
     if (condition(array[i]))
       array[iOut++] = array[i];
   array.length = iOut;
}

// Try it out.  A quadratic solution would take a very long time.
var n = 1*1000*1000;
console.log("constructing array...");
var Auction = {auctions: []};
for (var i = 0; i < n; ++i) {
  Auction.auctions.push({seconds:1});
  Auction.auctions.push({seconds:2});
  Auction.auctions.push({seconds:0});
}
console.log("array length should be "+(3*n)+": ", Auction.auctions.length)
filterInPlace(Auction.auctions, function(auction) {return --auction.seconds >= 0; })
console.log("array length should be "+(2*n)+": ", Auction.auctions.length)
filterInPlace(Auction.auctions, function(auction) {return --auction.seconds >= 0; })
console.log("array length should be "+n+": ", Auction.auctions.length)
filterInPlace(Auction.auctions, function(auction) {return --auction.seconds >= 0; })
console.log("array length should be 0: ", Auction.auctions.length)

Tenga en cuenta que esto modifica la matriz original en lugar de crear una nueva matriz; hacerlo de esta manera puede ser ventajoso, por ejemplo, en el caso de que la matriz sea el único cuello de botella de memoria del programa; en ese caso, no desea crear otra matriz del mismo tamaño, ni siquiera temporalmente.

Don Hatch
fuente
¡Nunca me di cuenta de que podía asignar la longitud de una matriz!
Michael
No sabía Array.splice(i,1)que crearía una nueva instancia de matriz cada vez. Estoy muy avergonzado.
Dehart el
2
@dehart Ha, bien :-) En realidad, no crea una nueva instancia de matriz cada vez; pero tiene que golpear cada elemento cuyo índice es mayor que i hacia abajo, que es en promedio n / 2 golpes.
Don Hatch, el
1

Ya hay muchas respuestas maravillosas en este hilo. Sin embargo, quería compartir mi experiencia cuando intenté resolver "eliminar el enésimo elemento de la matriz" en el contexto de ES5.

Las matrices de JavaScript tienen diferentes métodos para agregar / eliminar elementos desde el inicio o el final. Estos son:

arr.push(ele) - To add element(s) at the end of the array 
arr.unshift(ele) - To add element(s) at the beginning of the array
arr.pop() - To remove last element from the array 
arr.shift() - To remove first element from the array 

Esencialmente, ninguno de los métodos anteriores se puede usar directamente para eliminar el enésimo elemento de la matriz.

Un hecho que vale la pena señalar es que esto está en contraste con el uso del iterador de Java, que es posible eliminar el enésimo elemento de una colección mientras se itera.

Básicamente, esto nos deja con un solo método de matriz Array.splicepara realizar la eliminación del enésimo elemento (también hay otras cosas que podría hacer con estos métodos, pero en el contexto de esta pregunta, me estoy centrando en la eliminación de elementos):

Array.splice(index,1) - removes the element at the index 

Aquí está el código copiado de la respuesta original (con comentarios):

var arr = ["one", "two", "three", "four"];
var i = arr.length; //initialize counter to array length 

while (i--) //decrement counter else it would run into IndexOutBounds exception
{
  if (arr[i] === "four" || arr[i] === "two") {
    //splice modifies the original array
    arr.splice(i, 1); //never runs into IndexOutBounds exception 
    console.log("Element removed. arr: ");

  } else {
    console.log("Element not removed. arr: ");
  }
  console.log(arr);
}

Otro método notable es Array.slice. Sin embargo, el tipo de retorno de este método son los elementos eliminados. Además, esto no modifica la matriz original. Fragmento de código modificado de la siguiente manera:

var arr = ["one", "two", "three", "four"];
var i = arr.length; //initialize counter to array length 

while (i--) //decrement counter 
{
  if (arr[i] === "four" || arr[i] === "two") {
    console.log("Element removed. arr: ");
    console.log(arr.slice(i, i + 1));
    console.log("Original array: ");
    console.log(arr);
  }
}

Dicho esto, aún podemos usar Array.slicepara eliminar el enésimo elemento como se muestra a continuación. Sin embargo, es mucho más código (por lo tanto, ineficiente)

var arr = ["one", "two", "three", "four"];
var i = arr.length; //initialize counter to array length 

while (i--) //decrement counter 
{
  if (arr[i] === "four" || arr[i] === "two") {
    console.log("Array after removal of ith element: ");
    arr = arr.slice(0, i).concat(arr.slice(i + 1));
    console.log(arr);
  }

}

El Array.slicemétodo es extremadamente importante para lograr la inmutabilidad en la programación funcional à la redux

Bhanuprakash D
fuente
Tenga en cuenta que más código no debe ser una medida de la eficiencia de un código.
kano
0

Intente retransmitir una matriz en newArray cuando realice un bucle:

var auctions = Auction.auctions;
var auctionIndex;
var auction;
var newAuctions = [];

for (
  auctionIndex = 0; 
  auctionIndex < Auction.auctions.length;
  auctionIndex++) {

  auction = auctions[auctionIndex];

  if (auction.seconds >= 0) { 
    newAuctions.push(
      auction);
  }    
}

Auction.auctions = newAuctions;
Zon
fuente
0

Dos ejemplos que funcionan:

(Example ONE)
// Remove from Listing the Items Checked in Checkbox for Delete
let temp_products_images = store.state.c_products.products_images
if (temp_products_images != null) {
    for (var l = temp_products_images.length; l--;) {
        // 'mark' is the checkbox field
        if (temp_products_images[l].mark == true) {
            store.state.c_products.products_images.splice(l,1);         // THIS WORKS
            // this.$delete(store.state.c_products.products_images,l);  // THIS ALSO WORKS
        }
    }
}

(Example TWO)
// Remove from Listing the Items Checked in Checkbox for Delete
let temp_products_images = store.state.c_products.products_images
if (temp_products_images != null) {
    let l = temp_products_images.length
    while (l--)
    {
        // 'mark' is the checkbox field
        if (temp_products_images[l].mark == true) {
            store.state.c_products.products_images.splice(l,1);         // THIS WORKS
            // this.$delete(store.state.c_products.products_images,l);  // THIS ALSO WORKS
        }
    }
}
Fred
fuente
0

Prueba esto

RemoveItems.forEach((i, j) => {
    OriginalItems.splice((i - j), 1);
});
Almiar
fuente
-2
for (i = 0, len = Auction.auctions.length; i < len; i++) {
    auction = Auction.auctions[i];
    Auction.auctions[i]['seconds'] --;
    if (auction.seconds < 0) {
        Auction.auctions.splice(i, 1);
        i--;
        len--;
    }
}
Dmitry Ragozin
fuente
77
Una buena respuesta siempre tendrá una explicación de lo que se hizo y por qué se hizo de esa manera, no solo para el OP sino para los futuros visitantes de SO.
B001 ᛦ
-2

Puedes mirar y usar shift()

usuario8533067
fuente
3
Agregue un ejemplo utilizando este método.
Ivan