Looping melalui array dan menghapus item, tanpa memutus perulangan

462

Saya memiliki berikut ini untuk loop, dan ketika saya gunakan splice()untuk menghapus item, saya kemudian mendapatkan bahwa 'detik' tidak terdefinisi. Saya dapat memeriksa apakah itu tidak terdefinisi, tetapi saya merasa mungkin ada cara yang lebih elegan untuk melakukan ini. Keinginannya adalah hanya menghapus item dan terus berjalan.

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
sumber
11
Selain iterasi mundur dan menyesuaikan panjang, Anda juga bisa hanya menempatkan anggota yang Anda inginkan ke dalam array baru.
RobG
2
Mengapa Anda mengatakan Auction.auctions[i]['seconds']--sebaliknya auction.seconds--?
Don Hatch
Anda mungkin ingin melihat fungsi yang telah ditentukan .shift ();
raku

Jawaban:

856

Array sedang diindeks ulang ketika Anda melakukan .splice(), yang berarti Anda akan melewati indeks ketika satu dihapus, dan cache Anda .lengthsudah usang.

Untuk memperbaikinya, Anda harus mengurangi isetelah .splice(), atau hanya mengulanginya secara terbalik ...

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

Dengan cara ini pengindeksan ulang tidak mempengaruhi item berikutnya dalam iterasi, karena pengindeksan hanya mempengaruhi item dari titik saat ini ke akhir Array, dan item berikutnya dalam iterasi lebih rendah dari titik saat ini.

user1106925
sumber
151

Ini adalah masalah yang cukup umum. Solusinya adalah mengulang ke belakang:

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

Tidak masalah jika Anda menghentikannya dari akhir karena indeks akan dipertahankan saat Anda mundur.

frattaro
sumber
48

Hitung ulang panjang setiap kali melalui loop bukan hanya pada awalnya, misalnya:

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

Dengan begitu Anda tidak akan melampaui batas.

EDIT: menambahkan penurunan dalam pernyataan if.

Marc
sumber
32

Meskipun pertanyaan Anda adalah tentang menghapus elemen dari array yang di-iterasi dan bukan tentang menghilangkan elemen (selain beberapa pemrosesan lainnya) secara efisien, saya pikir kita harus mempertimbangkannya kembali jika dalam situasi yang sama.

Kompleksitas algoritmik dari pendekatan ini adalah O(n^2)sebagai fungsi splice dan for loop keduanya iterate atas array (fungsi splice menggeser semua elemen array dalam kasus terburuk). Sebagai gantinya Anda hanya bisa mendorong elemen yang diperlukan ke array baru dan kemudian hanya menetapkan array itu ke variabel yang diinginkan (yang baru saja diulangi).

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;

Karena ES2015 dapat kita gunakan Array.prototype.filteruntuk menyesuaikan semuanya dalam satu baris:

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

Jika Anda menggunakan ES6 + - mengapa tidak menggunakan metode Array.filter saja?

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

Perhatikan bahwa memodifikasi elemen array selama iterasi filter hanya berfungsi untuk objek dan tidak akan berfungsi untuk array nilai primitif.

Rubinsh
sumber
9

Solusi sederhana lain untuk mencerna elemen array sekali:

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

Berikut adalah contoh lain untuk penggunaan sambungan yang tepat. Contoh ini akan menghapus 'atribut' dari 'array'.

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

Untuk setiap orang yang telah menjawab pertanyaan yang sangat mendasar ini dengan kode yang memiliki splice () dalam satu lingkaran, yang telah menjalankan waktu O (n 2 ), atau yang telah memutakhirkan jawaban seperti itu, selama tujuh tahun sejak pertanyaan ini diposting: Anda harus malu .

Berikut adalah solusi waktu linear sederhana untuk masalah waktu linear sederhana ini.

Ketika saya menjalankan cuplikan ini, dengan n = 1 juta, setiap panggilan untuk memfilterInPlace () membutuhkan 0,013 hingga 0,016 detik. Solusi kuadratik (mis. Jawaban yang diterima) akan membutuhkan jutaan kali lipat, atau lebih.

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

Perhatikan bahwa ini memodifikasi array asli di tempat daripada membuat array baru; melakukannya di tempat seperti ini bisa menguntungkan, misalnya dalam hal array adalah hambatan memori tunggal program; dalam hal ini, Anda tidak ingin membuat array lain dengan ukuran yang sama, bahkan untuk sementara.

Don Hatch
sumber
Saya tidak pernah menyadari Anda bisa menetapkan panjang array!
Michael
Saya tidak tahu Array.splice(i,1)akan membuat contoh array baru setiap kali. Saya sangat malu.
dehart
2
@dehart Ha, bagus :-) Sebenarnya tidak membuat instance array baru setiap kali; tetapi memang harus menabrak setiap item yang indeksnya lebih besar dari saya ke bawah, yang rata-rata n / 2 benjolan.
Don Hatch
1

Sudah banyak jawaban indah di utas ini. Namun saya ingin berbagi pengalaman ketika saya mencoba menyelesaikan "menghapus elemen ke-n dari array" dalam konteks ES5.

Array JavaScript memiliki metode berbeda untuk menambah / menghapus elemen dari awal atau akhir. Ini adalah:

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 

Pada dasarnya tidak ada metode di atas yang dapat digunakan secara langsung untuk menghapus elemen ke-n dari array.

Fakta yang perlu dicatat adalah bahwa ini kontras dengan penggunaan java iterator yang memungkinkan untuk menghapus elemen ke-n untuk koleksi saat iterasi.

Ini pada dasarnya membuat kita hanya memiliki satu metode array Array.spliceuntuk melakukan penghapusan elemen ke-n (ada hal-hal lain yang dapat Anda lakukan dengan metode ini juga, tetapi dalam konteks pertanyaan ini saya fokus pada penghapusan elemen):

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

Berikut adalah kode yang disalin dari jawaban asli (dengan komentar):

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

Metode penting lainnya adalah Array.slice. Namun tipe pengembalian dari metode ini adalah elemen yang dihapus. Juga ini tidak mengubah array asli. Cuplikan kode yang dimodifikasi sebagai berikut:

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

Karena itu, kita masih bisa menggunakan Array.sliceuntuk menghapus elemen n seperti yang ditunjukkan di bawah ini. Namun itu lebih banyak kode (karenanya tidak efisien)

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

}

The Array.sliceMetode ini sangat penting untuk mencapai kekekalan dalam pemrograman fungsional à la redux

Bhanuprakash D
sumber
Perhatikan bahwa lebih banyak kode tidak boleh menjadi ukuran efisiensi kode.
Kano
0

Cobalah untuk menyampaikan array ke newArray saat mengulang:

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

Dua contoh yang berhasil:

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

Cobalah ini

RemoveItems.forEach((i, j) => {
    OriginalItems.splice((i - j), 1);
});
Rick
sumber
-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
sumber
7
Sebuah jawaban yang baik akan selalu memiliki penjelasan tentang apa yang dilakukan dan mengapa itu dilakukan sedemikian rupa, tidak hanya untuk OP tetapi untuk pengunjung masa depan ke SO.
B001 ᛦ
-2

Anda bisa melihat dan menggunakannya shift()

pengguna8533067
sumber
3
Silakan tambahkan contoh menggunakan metode ini.
Ivan