Passare attraverso l'array e rimuovere gli elementi, senza interrompere il loop


462

Ho il seguente ciclo per, e quando uso splice()per rimuovere un oggetto, ottengo che i "secondi" non sono definiti. Potrei verificare se non è definito, ma penso che probabilmente ci sia un modo più elegante per farlo. Il desiderio è semplicemente eliminare un oggetto e continuare.

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

11
Oltre a scorrere indietro e regolare la lunghezza, puoi anche inserire i membri desiderati in un nuovo array.
RobG

2
Perché dici Auction.auctions[i]['seconds']--invece di auction.seconds--?
Don Hatch,

probabilmente vorrai esaminare la funzione predefinita .shift ();
Raku,

Risposte:


856

L'array viene reindicizzato quando si esegue un .splice(), il che significa che si salterà su un indice quando viene rimosso e la cache .lengthè obsoleta.

Per risolverlo, dovresti o decrementare idopo un .splice(), o semplicemente iterare al contrario ...

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

In questo modo la reindicizzazione non influisce sull'elemento successivo nell'iterazione, poiché l'indicizzazione influisce solo sugli elementi dal punto corrente alla fine dell'array e l'elemento successivo nell'iterazione è inferiore al punto corrente.


151

Questo è un problema piuttosto comune. La soluzione è eseguire il loop indietro:

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

Non importa se li stai saltando fuori dalla fine perché gli indici saranno preservati mentre vai indietro.


48

Ricalcola la lunghezza ogni volta attraverso il loop anziché solo all'inizio, ad es .:

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

In questo modo non supererai i limiti.

EDIT: aggiunto un decremento nell'istruzione if.


32

Sebbene la tua domanda riguardi l'eliminazione di elementi dall'array su cui si sta ripetendo e non la rimozione efficiente di elementi (oltre a qualche altra elaborazione), penso che si dovrebbe riconsiderarlo se si trova in una situazione simile.

La complessità algoritmica di questo approccio è O(n^2)come la funzione splice e il ciclo for entrambi scorre sull'array (la funzione splice sposta tutti gli elementi dell'array nel caso peggiore). Invece puoi semplicemente inviare gli elementi richiesti al nuovo array e quindi assegnare quell'array alla variabile desiderata (che è stata appena ripetuta).

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;

Dal momento che ES2015 possiamo usare Array.prototype.filterper adattare tutto in una riga:

Auction.auctions = Auction.auctions.filter(auction => --auction.seconds >= 0);

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

10

Se stai usando ES6 + - perché non usare semplicemente il metodo Array.filter?

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

Si noti che la modifica dell'elemento array durante l'iterazione del filtro funziona solo per gli oggetti e non per l'array di valori primitivi.


9

Un'altra soluzione semplice per digerire una volta gli elementi di un array:

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
}

8

Ecco un altro esempio per l'uso corretto della giunzione. Questo esempio sta per rimuovere "attributo" da "array".

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

8

A ogni persona che ha risposto a questa domanda basilare con il codice che ha splice () in un ciclo, che ha il tempo di esecuzione O (n 2 ), o che ha votato a favore di tale risposta, durante i sette anni successivi alla pubblicazione di questa domanda: dovresti vergognarsi .

Ecco una semplice soluzione di tempo lineare a questo semplice problema di tempo lineare.

Quando eseguo questo frammento, con n = 1 milione, ogni chiamata a filterInPlace () impiega da .013 a .016 secondi. Una soluzione quadratica (ad esempio la risposta accettata) richiederebbe circa un milione di volte.

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

Si noti che ciò modifica l'array originale in atto anziché creare un nuovo array; farlo in questo modo può essere vantaggioso, ad esempio nel caso in cui l'array sia il collo di bottiglia della memoria singola del programma; in tal caso, non si desidera creare un altro array della stessa dimensione, anche temporaneamente.


Non ho mai capito che potresti assegnare la lunghezza di un array!
Michael,

Non sapevo che Array.splice(i,1)avrebbe creato una nuova istanza di array ogni volta. Mi vergogno molto.
Dehart,

2
@dehart Ha, buono :-) In realtà non crea una nuova istanza di array ogni volta; ma deve eseguire il bump di ogni elemento il cui indice è maggiore di i verso il basso, che è in media n / 2 dossi.
Don Hatch,

1

Ci sono già molte risposte meravigliose su questa discussione. Tuttavia, volevo condividere la mia esperienza quando ho cercato di risolvere "rimuovi l'ennesimo elemento dall'array" nel contesto ES5.

Le matrici JavaScript hanno metodi diversi per aggiungere / rimuovere elementi dall'inizio o dalla fine. Questi sono:

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 

In sostanza, nessuno dei metodi precedenti può essere utilizzato direttamente per rimuovere l'ennesimo elemento dall'array.

Un fatto degno di nota è che questo è in contrasto con l'uso di java iterator, in cui è possibile rimuovere l'ennesimo elemento per una raccolta durante l'iterazione.

Questo in sostanza ci lascia con un solo metodo array Array.spliceper eseguire la rimozione dell'ennesimo elemento (ci sono altre cose che potresti fare anche con questi metodi, ma nel contesto di questa domanda mi sto concentrando sulla rimozione degli elementi):

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

Ecco il codice copiato dalla risposta originale (con commenti):

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

Un altro metodo degno di nota è Array.slice. Tuttavia, il tipo restituito di questo metodo sono gli elementi rimossi. Inoltre, ciò non modifica l'array originale. Snippet di codice modificato come segue:

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

Detto questo, possiamo ancora usare Array.sliceper rimuovere l'ennesimo elemento come mostrato di seguito. Tuttavia è molto più codice (quindi inefficiente)

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

}

Il Array.slicemetodo è estremamente importante per ottenere l'immutabilità nella programmazione funzionale alla redux


Si noti che più codice non dovrebbe essere una misura dell'efficienza di un codice.
kano,

0

Prova a inoltrare un array in newArray durante il 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;

0

Due esempi che funzionano:

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

0

Prova questo

RemoveItems.forEach((i, j) => {
    OriginalItems.splice((i - j), 1);
});

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

7
Una buona risposta avrà sempre una spiegazione di ciò che è stato fatto e del perché è stato fatto in questo modo, non solo per l'OP ma anche per i futuri visitatori di SO.
B001 ᛦ

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.