Durchlaufen des Arrays und Entfernen von Elementen, ohne die Schleife zu unterbrechen

462

Ich habe die folgende for-Schleife, und wenn ich splice()ein Element entferne, erhalte ich, dass 'Sekunden' undefiniert sind. Ich könnte überprüfen, ob es undefiniert ist, aber ich denke, es gibt wahrscheinlich einen eleganteren Weg, dies zu tun. Der Wunsch ist, einfach einen Artikel zu löschen und weiterzumachen.

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
quelle
11
Sie können nicht nur rückwärts iterieren und die Länge anpassen, sondern auch die gewünschten Elemente in ein neues Array einfügen.
RobG
2
Warum sagst du Auction.auctions[i]['seconds']--statt auction.seconds--?
Don Hatch
Sie möchten wahrscheinlich in die vordefinierte Funktion .shift () schauen.
Raku

Antworten:

856

Das Array wird neu indiziert, wenn Sie a .splice()ausführen. Dies bedeutet, dass Sie einen Index überspringen, wenn einer entfernt wird, und Ihr zwischengespeicherter Index .lengthveraltet ist.

Um es zu beheben, würden Sie müssen entweder zu verringern , inachdem ein .splice()oder einfach Iterierte in umgekehrter ...

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

Auf diese Weise wirkt sich die Neuindizierung nicht auf das nächste Element in der Iteration aus, da die Indizierung nur die Elemente vom aktuellen Punkt bis zum Ende des Arrays betrifft und das nächste Element in der Iteration niedriger als der aktuelle Punkt ist.

Benutzer1106925
quelle
151

Dies ist ein ziemlich häufiges Problem. Die Lösung besteht darin, rückwärts zu schleifen:

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

Es spielt keine Rolle, ob Sie sie am Ende entfernen, da die Indizes erhalten bleiben, wenn Sie rückwärts gehen.

Frattaro
quelle
48

Berechnen Sie die Länge jedes Mal durch die Schleife neu, anstatt nur zu Beginn, z.

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

Auf diese Weise werden Sie die Grenzen nicht überschreiten.

BEARBEITEN: In der if-Anweisung wurde ein Dekrement hinzugefügt.

Marc
quelle
32

Obwohl es bei Ihrer Frage darum geht, Elemente aus dem Array zu löschen, auf dem iteriert wird, und nicht darum, Elemente (zusätzlich zu einer anderen Verarbeitung) effizient zu entfernen, sollte man sie in einer ähnlichen Situation überdenken.

Die algorithmische Komplexität dieses Ansatzes besteht darin, dass O(n^2)die Spleißfunktion und die for-Schleife beide über das Array iterieren (die Spleißfunktion verschiebt im schlimmsten Fall alle Elemente des Arrays). Stattdessen können Sie einfach die erforderlichen Elemente in das neue Array verschieben und dieses Array dann der gewünschten Variablen zuweisen (die gerade wiederholt wurde).

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;

Seit ES2015 können wir Array.prototype.filteralles in einer Zeile zusammenfassen:

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

Wenn Sie ES6 + verwenden, warum nicht einfach die Array.filter-Methode verwenden?

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

Beachten Sie, dass das Ändern des Array-Elements während der Filteriteration nur für Objekte und nicht für Arrays primitiver Werte funktioniert.

Rubinsh
quelle
9

Eine weitere einfache Lösung, um Array-Elemente einmal zu verarbeiten:

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
quelle
8

Hier ist ein weiteres Beispiel für die ordnungsgemäße Verwendung von Spleiß. In diesem Beispiel wird 'Attribut' aus 'Array' entfernt.

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

An jede Person, die diese sehr grundlegende Frage mit Code mit splice () in einer Schleife beantwortet hat, die Laufzeit O (n 2 ) hat, oder die eine solche Antwort in den sieben Jahren seit dem Posten dieser Frage positiv bewertet hat: Sie sollten schäme dich .

Hier ist eine einfache lineare Zeitlösung für dieses einfache lineare Zeitproblem.

Wenn ich dieses Snippet mit n = 1 Million ausführe, dauert jeder Aufruf von filterInPlace () 0,013 bis 0,016 Sekunden. Eine quadratische Lösung (z. B. die akzeptierte Antwort) würde ungefähr das Millionenfache dauern.

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

Beachten Sie, dass dadurch das ursprüngliche Array geändert wird, anstatt ein neues Array zu erstellen. Dies an Ort und Stelle zu tun, kann vorteilhaft sein, z. B. in dem Fall, dass das Array der einzige Speicherengpass des Programms ist. In diesem Fall möchten Sie auch vorübergehend kein weiteres Array mit derselben Größe erstellen.

Don Hatch
quelle
Ich hätte nie gedacht, dass Sie die Länge eines Arrays zuweisen können!
Michael
Ich wusste nicht, Array.splice(i,1)dass jedes Mal eine neue Array-Instanz erstellt werden würde. Ich bin sehr beschämt.
Dehart
2
@dehart Ha, gut :-) Eigentlich wird nicht jedes Mal eine neue Array-Instanz erstellt; Es muss jedoch jedes Element, dessen Index größer als i ist, nach unten stoßen, was im Durchschnitt n / 2 Unebenheiten entspricht.
Don Hatch
1

Es gibt bereits viele wundervolle Antworten auf diesen Thread. Ich wollte jedoch meine Erfahrungen teilen, als ich versuchte, "n-tes Element aus Array entfernen" im ES5-Kontext zu lösen.

JavaScript-Arrays verfügen über verschiedene Methoden zum Hinzufügen / Entfernen von Elementen vom Anfang oder Ende. Diese sind:

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 

Im Wesentlichen kann keine der oben genannten Methoden direkt verwendet werden, um das n-te Element aus dem Array zu entfernen.

Bemerkenswert ist, dass dies im Gegensatz zu Java-Iteratoren steht, mit denen das n-te Element für eine Sammlung während der Iteration entfernt werden kann.

Dies lässt uns im Grunde nur eine Array-Methode Array.splicezum Entfernen des n-ten Elements übrig (es gibt andere Dinge, die Sie auch mit diesen Methoden tun könnten, aber im Zusammenhang mit dieser Frage konzentriere ich mich auf das Entfernen von Elementen):

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

Hier ist der Code, der von der ursprünglichen Antwort (mit Kommentaren) kopiert wurde:

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

Eine andere bemerkenswerte Methode ist Array.slice. Der Rückgabetyp dieser Methode sind jedoch die entfernten Elemente. Auch dies ändert das ursprüngliche Array nicht. Geändertes Code-Snippet wie folgt:

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

Trotzdem können wir das Array.slicen-te Element wie unten gezeigt entfernen. Es ist jedoch viel mehr Code (daher ineffizient)

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

}

Die Array.sliceMethode ist äußerst wichtig, um Unveränderlichkeit bei der funktionalen Programmierung à la Redux zu erreichen

Bhanuprakash D.
quelle
Beachten Sie, dass mehr Code kein Maß für die Effizienz eines Codes sein sollte.
Kano
0

Versuchen Sie, ein Array in einer Schleife an newArray weiterzuleiten:

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

Zwei Beispiele, die funktionieren:

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

Probieren Sie es aus

RemoveItems.forEach((i, j) => {
    OriginalItems.splice((i - j), 1);
});
Rick
quelle
-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
quelle
7
Eine gute Antwort wird immer eine Erklärung darüber enthalten, was getan wurde und warum es so getan wurde, nicht nur für das OP, sondern auch für zukünftige Besucher von SO.
B001
-2

Sie können einfach durchsehen und verwenden shift()

user8533067
quelle
3
Bitte fügen Sie mit dieser Methode ein Beispiel hinzu.
Ivan