Loop através da matriz e remoção de itens, sem interromper o loop

462

Eu tenho o seguinte para o loop e, quando uso splice()para remover um item, obtenho que 'segundos' é indefinido. Eu poderia verificar se está indefinido, mas acho que provavelmente há uma maneira mais elegante de fazer isso. O desejo é simplesmente excluir um item e 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
fonte
11
Além de iterar para trás e ajustar o comprimento, você também pode colocar os membros que deseja em uma nova matriz.
RobG 27/03
2
Por que você diz em Auction.auctions[i]['seconds']--vez de auction.seconds--?
Don escotilha
você provavelmente quer olhar para a função predefinida .shift ();
raku

Respostas:

856

A matriz está sendo indexada novamente quando você faz um .splice(), o que significa que você pulará um índice quando um for removido e seu cache .lengthestiver obsoleto.

Para corrigi-lo, você precisa decrementar iapós a .splice()ou simplesmente iterar ao contrário ...

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

Dessa forma, a reindexação não afeta o próximo item na iteração, pois a indexação afeta apenas os itens do ponto atual até o final da Matriz, e o próximo item na iteração é menor que o ponto atual.

user1106925
fonte
151

Esse é um problema bastante comum. A solução é fazer um loop para trá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);
    }
}

Não importa se você os está retirando do final, porque os índices serão preservados à medida que você retrocede.

frattaro
fonte
48

Recalcule o comprimento de cada vez através do loop em vez de apenas no início, por exemplo:

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

Dessa forma, você não excederá os limites.

EDIT: adicionado um decremento na instrução if.

Marc
fonte
32

Embora sua pergunta seja sobre a exclusão de elementos da matriz que está sendo iterada e não sobre a remoção eficiente de elementos (além de algum outro processamento), acho que se deve reconsiderá-lo se estiver em situação semelhante.

A complexidade algorítmica dessa abordagem é O(n^2)como função de emenda e o loop for itera sobre a matriz (a função de emenda muda todos os elementos da matriz no pior caso). Em vez disso, você pode simplesmente enviar os elementos necessários para a nova matriz e, em seguida, atribuir essa matriz à variável desejada (que foi iterada).

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 o ES2015, podemos usar Array.prototype.filtertudo isso em uma linha:

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

Se você estiver usando o ES6 + - por que não usar o método Array.filter?

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

Observe que a modificação do elemento da matriz durante a iteração do filtro funciona apenas para objetos e não para a matriz de valores primitivos.

Rubinsh
fonte
9

Outra solução simples para digerir os elementos de uma matriz uma 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
fonte
8

Aqui está outro exemplo para o uso adequado de emenda. Este exemplo está prestes a remover 'attribute' de 'array'.

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

Para todas as pessoas que responderam a essa pergunta muito básica com código splice () em um loop, que tenha o tempo de execução O (n 2 ), ou que tenha votado positivamente nessa resposta, durante os sete anos desde que essa pergunta foi publicada: você deve tenha vergonha .

Aqui está uma solução de tempo linear simples para esse problema de tempo linear simples.

Quando executo esse snippet, com n = 1 milhão, cada chamada para filterInPlace () leva 0,013 a 0,016 segundos. Uma solução quadrática (por exemplo, a resposta aceita) levaria um milhão de vezes mais ou 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)

Observe que isso modifica a matriz original no lugar, em vez de criar uma nova matriz; fazê-lo no lugar como esse pode ser vantajoso, por exemplo, no caso em que a matriz é o gargalo de memória única do programa; nesse caso, você não deseja criar outra matriz do mesmo tamanho, mesmo que temporariamente.

Don Hatch
fonte
Eu nunca percebi que você poderia atribuir o comprimento de uma matriz!
Michael
Eu não sabia Array.splice(i,1)que criaria uma nova instância de matriz a cada vez. Estou muito envergonhado.
DeHart
2
@ Dehart Ha, bom :-) Na verdade, ele não cria uma nova instância de matriz de cada vez; mas precisa aumentar cada item cujo índice é maior que i para baixo, o que é, em média, n / 2 solavancos.
Don escotilha
1

Já existem muitas respostas maravilhosas neste tópico. No entanto, eu queria compartilhar minha experiência ao tentar resolver "remover o enésimo elemento da matriz" no contexto do ES5.

As matrizes JavaScript têm métodos diferentes para adicionar / remover elementos do início ou do fim. Esses são:

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 

Essencialmente, nenhum dos métodos acima pode ser usado diretamente para remover o enésimo elemento da matriz.

Um fato digno de nota é que isso contrasta com o uso do java iterator, no qual é possível remover o enésimo elemento de uma coleção durante a iteração.

Isso basicamente nos deixa com apenas um método de matriz Array.splicepara executar a remoção do enésimo elemento (há outras coisas que você poderia fazer com esses métodos também, mas no contexto desta pergunta, estou focando na remoção de elementos):

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

Aqui está o código copiado da resposta original (com comentários):

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

Outro método digno de nota é Array.slice. No entanto, o tipo de retorno desse método são os elementos removidos. Além disso, isso não modifica a matriz original. Fragmento de código modificado da seguinte maneira:

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

Dito isto, ainda podemos usar Array.slicepara remover o enésimo elemento, como mostrado abaixo. No entanto, é muito mais código (portanto 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);
  }

}

O Array.slicemétodo é extremamente importante para obter imutabilidade na programação funcional à la redux

Bhanuprakash D
fonte
Observe que mais código não deve ser uma medida da eficiência de um código.
kano 2/04
0

Tente retransmitir uma matriz para newArray ao fazer um loop:

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

Dois exemplos que funcionam:

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

Faça uma tentativa

RemoveItems.forEach((i, j) => {
    OriginalItems.splice((i - j), 1);
});
Rick
fonte
-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
fonte
7
Uma boa resposta sempre terá uma explicação do que foi feito e por que foi feito dessa maneira, não apenas para o OP, mas para futuros visitantes do SO.
B001 #
-2

Você pode apenas olhar e usar shift()

user8533067
fonte
3
Por favor, adicione um exemplo usando este método.
Ivan Ivan