Pętla przez tablicę i usuwanie elementów, bez przerywania pętli

462

Mam następujące dla pętli for i kiedy używam splice()do usunięcia elementu, wtedy dostaję, że „sekundy” są niezdefiniowane. Mogę sprawdzić, czy jest niezdefiniowany, ale wydaje mi się, że istnieje bardziej elegancki sposób na zrobienie tego. Chcemy po prostu usunąć element i kontynuować.

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
źródło
11
Oprócz iteracji do tyłu i dostosowywania długości, możesz również po prostu umieścić wybrane elementy w nowej tablicy.
RobG
2
Dlaczego mówisz Auction.auctions[i]['seconds']--zamiast auction.seconds--?
Don Hatch
prawdopodobnie chcesz zajrzeć do predefiniowanej funkcji .shift ();
raku

Odpowiedzi:

856

Tablica jest ponownie indeksowana po wykonaniu .splice(), co oznacza, że ​​pominiesz indeks, gdy zostanie on usunięty, a Twoja pamięć podręczna .lengthjest przestarzała.

Aby to naprawić, musisz albo zmniejszyć ipo .splice(), albo po prostu iterować w odwrotnej kolejności ...

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

W ten sposób ponowne indeksowanie nie wpływa na następny element w iteracji, ponieważ indeksowanie wpływa tylko na elementy od bieżącego punktu do końca tablicy, a następny element w iteracji jest niższy niż bieżący punkt.

1106925
źródło
151

To dość powszechny problem. Rozwiązaniem jest zapętlenie wstecz:

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

Nie ma znaczenia, czy usuniesz je z końca, ponieważ indeksy zostaną zachowane podczas cofania.

frattaro
źródło
47

Przelicz długość za każdym razem przez pętlę zamiast na samym początku, np .:

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

W ten sposób nie przekroczysz granic.

EDYCJA: dodano zmniejszenie w instrukcji if.

Marc
źródło
32

Chociaż twoje pytanie dotyczy usuwania elementów z tablicy, która jest iterowana, a nie wydajnego usuwania elementów (oprócz niektórych innych metod przetwarzania), myślę, że należy je ponownie rozważyć, jeśli w podobnej sytuacji.

Złożoność algorytmiczna tego podejścia jest O(n^2)jak funkcja łączenia, a pętla for iteruje się po tablicy (funkcja łączenia przesuwa wszystkie elementy tablicy w najgorszym przypadku). Zamiast tego możesz po prostu wepchnąć wymagane elementy do nowej tablicy, a następnie po prostu przypisać tę tablicę do pożądanej zmiennej (która była właśnie iterowana).

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;

Od wersji ES2015 możemy Array.prototype.filterto wszystko zmieścić w jednej linii:

Auction.auctions = Auction.auctions.filter(auction => --auction.seconds >= 0);
0xc0de
źródło
22
Auction.auctions = Auction.auctions.filter(function(el) {
  return --el["seconds"] > 0;
});
Esteta
źródło
10

Jeśli używasz ES6 + - dlaczego nie skorzystać z metody Array.filter?

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

Zauważ, że modyfikacja elementu tablicy podczas iteracji filtra działa tylko dla obiektów i nie będzie działać dla tablicy pierwotnych wartości.

Rubinsh
źródło
9

Kolejne proste rozwiązanie do jednorazowego trawienia elementów tablicy:

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
źródło
8

Oto kolejny przykład prawidłowego użycia łączenia. Ten przykład ma zamiar usunąć „atrybut” z „tablicy”.

for (var i = array.length; i--;) {
    if (array[i] === 'attribute') {
        array.splice(i, 1);
    }
}
daniel.szaniszlo
źródło
8

Każdej osobie, która odpowiedziała na to bardzo podstawowe pytanie kodem posiadającym splice () w pętli, która ma czas wykonania O (n 2 ), lub która poparła taką odpowiedź, w ciągu siedmiu lat od opublikowania tego pytania: powinieneś wstydzić się .

Oto proste rozwiązanie czasu liniowego dla tego prostego problemu czasu liniowego.

Kiedy uruchamiam ten fragment kodu, przy n = 1 milion, każde wywołanie filterInPlace () zajmuje od 0,013 do 0,016 sekundy. Rozwiązanie kwadratowe (np. Zaakceptowana odpowiedź) zajęłoby milion razy mniej więcej tyle.

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

Zauważ, że modyfikuje to oryginalną tablicę zamiast tworzyć nową tablicę; robienie tego w ten sposób może być korzystne, np. w przypadku, gdy tablica jest wąskim gardłem pojedynczej pamięci programu; w takim przypadku nie chcesz tworzyć innej tablicy o tym samym rozmiarze, nawet tymczasowo.

Don Hatch
źródło
Nigdy nie zdawałem sobie sprawy, że możesz przypisać długość tablicy!
Michael
Nie wiedziałem, Array.splice(i,1)że za każdym razem utworzę nową instancję tablicy. Jestem bardzo zawstydzony.
dehart
2
@dehart Ha, dobrze :-) W rzeczywistości za każdym razem nie tworzy nowej instancji tablicy; ale musi uderzać w każdy element, którego indeks jest większy niż i w dół, co stanowi średnio n / 2 uderzeń.
Don Hatch,
1

W tym wątku jest już wiele wspaniałych odpowiedzi. Chciałem jednak podzielić się swoim doświadczeniem, gdy próbowałem rozwiązać „usunięcie n-tego elementu z tablicy” w kontekście ES5.

Macierze JavaScript mają różne metody dodawania / usuwania elementów od początku lub końca. To są:

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 

Zasadniczo żadnej z powyższych metod nie można użyć bezpośrednio do usunięcia n-tego elementu z tablicy.

Faktem wartym odnotowania jest to, że kontrastuje to z iteratorem Java, w którym można iterować n-ty element kolekcji.

Zasadniczo pozostawia nam to tylko jedną metodę tablicową Array.splicedo wykonania usuwania n-tego elementu (są też inne rzeczy, które możesz zrobić z tymi metodami, ale w kontekście tego pytania skupiam się na usuwaniu elementów):

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

Oto kod skopiowany z oryginalnej odpowiedzi (z komentarzami):

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

Inną godną uwagi metodą jest Array.slice. Jednak typem zwrotnym tej metody są usunięte elementy. Nie zmienia to również oryginalnej tablicy. Zmodyfikowano fragment kodu w następujący sposób:

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

To powiedziawszy, nadal możemy użyć Array.slicedo usunięcia n-tego elementu, jak pokazano poniżej. Jest to jednak znacznie więcej kodu (stąd nieefektywne)

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

}

Array.sliceMetoda jest niezwykle ważne, aby osiągnąć niezmienność w programowaniu funkcjonalnym à la Redux

Bhanuprakash D.
źródło
Zauważ, że więcej kodu nie powinno być miarą wydajności kodu.
kano
0

Spróbuj przekształcić tablicę w newArray podczas zapętlania:

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;
Strefa
źródło
0

Dwa przykłady, które działają:

(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
źródło
0

Spróbuj

RemoveItems.forEach((i, j) => {
    OriginalItems.splice((i - j), 1);
});
Stóg
źródło
-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
źródło
7
Dobra odpowiedź zawsze będzie wyjaśnienie, co się stało i dlaczego to było zrobione w taki sposób, nie tylko dla PO, ale dla przyszłych turystów do tego.
B001 ᛦ
-2

Możesz po prostu przejrzeć i użyć shift()

użytkownik8533067
źródło
3
Dodaj przykład przy użyciu tej metody.
Ivan