Vòng qua mảng và loại bỏ các mục, mà không phá vỡ vòng lặp


462

Tôi có vòng lặp sau đây và khi tôi sử dụng splice()để xóa một mục, sau đó tôi nhận được 'giây' không xác định. Tôi có thể kiểm tra nếu nó không được xác định, nhưng tôi cảm thấy có lẽ có một cách thanh lịch hơn để làm điều này. Mong muốn chỉ đơn giản là xóa một mục và tiếp tục đi.

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
Ngoài việc lặp lại và điều chỉnh độ dài, bạn cũng có thể chỉ cần đặt các thành viên bạn muốn vào một mảng mới.
RobG

2
Tại sao bạn nói Auction.auctions[i]['seconds']--thay vì auction.seconds--?
Don nở

bạn có thể muốn xem xét hàm được xác định trước .shift ();
raku

Câu trả lời:


856

Mảng đang được lập chỉ mục lại khi bạn thực hiện .splice(), điều đó có nghĩa là bạn sẽ bỏ qua một chỉ mục khi một mục bị xóa và bộ nhớ cache của bạn .lengthđã lỗi thời.

Để khắc phục, bạn cần phải giảm isau một .splice()hoặc đơn giản là lặp lại ...

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

Bằng cách này, việc lập chỉ mục lại không ảnh hưởng đến mục tiếp theo trong lần lặp, vì việc lập chỉ mục chỉ ảnh hưởng đến các mục từ điểm hiện tại đến cuối Mảng và mục tiếp theo trong lần lặp thấp hơn điểm hiện tại.


151

Đây là một vấn đề khá phổ biến. Giải pháp là lặp ngược lại:

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

Sẽ không có vấn đề gì nếu bạn bật chúng ra khỏi cuối vì các chỉ số sẽ được giữ nguyên khi bạn đi lùi.


48

Tính toán lại độ dài mỗi lần qua vòng lặp thay vì chỉ ở đầu, ví dụ:

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

Bằng cách đó bạn sẽ không vượt quá giới hạn.

EDIT: thêm một giảm trong câu lệnh if.


32

Mặc dù câu hỏi của bạn là về việc xóa các phần tử khỏi mảng được lặp đi lặp lại và không phải về việc loại bỏ các phần tử (ngoài một số xử lý khác) một cách hiệu quả, tôi nghĩ người ta nên xem xét lại nếu trong tình huống tương tự.

Độ phức tạp thuật toán của phương pháp này O(n^2)là hàm splice và vòng lặp for lặp cả trên mảng (hàm splice làm dịch chuyển tất cả các phần tử của mảng trong trường hợp xấu nhất). Thay vào đó, bạn chỉ có thể đẩy các phần tử cần thiết sang mảng mới và sau đó chỉ định mảng đó cho biến mong muốn (chỉ được lặp lại theo).

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;

Kể từ ES2015, chúng tôi có thể sử dụng Array.prototype.filterđể phù hợp với tất cả trong một dòng:

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

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

10

Nếu bạn đang sử dụng ES6 + - tại sao không sử dụng phương thức Array.filter?

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

Lưu ý rằng sửa đổi phần tử mảng trong quá trình lặp bộ lọc chỉ hoạt động cho các đối tượng và sẽ không hoạt động đối với mảng các giá trị nguyên thủy.


9

Một giải pháp đơn giản khác để tiêu hóa một phần tử mảng một lần:

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

Dưới đây là một ví dụ khác cho việc sử dụng mối nối đúng cách. Ví dụ này sắp xóa 'thuộc tính' khỏi 'mảng'.

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

8

Đối với mọi người đã trả lời câu hỏi rất cơ bản này với mã có splice () trong một vòng lặp, có thời gian chạy O (n 2 ) hoặc người đã đưa ra câu trả lời như vậy, trong bảy năm kể từ khi câu hỏi này được đăng: bạn nên phải xấu hổ .

Dưới đây là một giải pháp thời gian tuyến tính đơn giản cho vấn đề thời gian tuyến tính đơn giản này.

Khi tôi chạy đoạn mã này, với n = 1 triệu, mỗi lệnh gọi tới bộ lọcInPlace () mất từ ​​2013 đến 0,016 giây. Một giải pháp bậc hai (ví dụ: câu trả lời được chấp nhận) sẽ mất một triệu lần như vậy.

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

Lưu ý rằng điều này sửa đổi mảng ban đầu thay vì tạo một mảng mới; thực hiện nó ở vị trí như thế này có thể là lợi thế, ví dụ trong trường hợp mảng là nút cổ chai bộ nhớ đơn của chương trình; trong trường hợp đó, bạn không muốn tạo một mảng khác có cùng kích thước, thậm chí là tạm thời.


Tôi không bao giờ nhận ra bạn có thể chỉ định chiều dài của một mảng!
Michael

Tôi không biết Array.splice(i,1)sẽ tạo ra một thể hiện mảng mới mỗi lần. Tôi rất xấu hổ.
bắt đầu

2
@dehart Ha, tốt :-) Thực tế nó không tạo ra một thể hiện mảng mới mỗi lần; nhưng nó phải giảm tất cả các mục có chỉ số lớn hơn i xuống, tức là trung bình n / 2 lần.
Don nở

1

Có rất nhiều câu trả lời tuyệt vời về chủ đề này rồi. Tuy nhiên tôi muốn chia sẻ kinh nghiệm của mình khi tôi cố gắng giải quyết "loại bỏ phần tử thứ n khỏi mảng" trong ngữ cảnh ES5.

Mảng JavaScript có các phương thức khác nhau để thêm / xóa các phần tử từ đầu hoặc cuối. Đó là:

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 

Về cơ bản, không có phương pháp nào ở trên có thể được sử dụng trực tiếp để loại bỏ phần tử thứ n khỏi mảng.

Một thực tế đáng chú ý là điều này trái ngược với java iterator sử dụng mà có thể loại bỏ phần tử thứ n cho một bộ sưu tập trong khi lặp.

Điều này về cơ bản khiến chúng ta chỉ có một phương thức mảng Array.spliceđể thực hiện loại bỏ phần tử thứ n (có những điều khác bạn có thể làm với các phương thức này, nhưng trong bối cảnh của câu hỏi này, tôi đang tập trung vào việc loại bỏ các phần tử):

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

Đây là mã được sao chép từ câu trả lời ban đầu (có ý kiến):

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

Một phương pháp đáng chú ý khác là Array.slice. Tuy nhiên, kiểu trả về của phương thức này là các phần tử bị loại bỏ. Ngoài ra, điều này không sửa đổi mảng ban đầu. Đoạn mã được sửa đổi như sau:

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

Tuy nhiên, chúng ta vẫn có thể sử dụng Array.sliceđể loại bỏ phần tử thứ n như hình bên dưới. Tuy nhiên, nó là nhiều mã hơn (do đó không hiệu quả)

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

}

Các Array.slicephương pháp là vô cùng quan trọng để đạt được tính bất biến trong lập trình chức năng à la Redux


Lưu ý rằng nhiều mã hơn không phải là thước đo hiệu quả của mã.
kano

0

Cố gắng chuyển tiếp một mảng vào newArray khi lặp:

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

Hai ví dụ hoạt động:

(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

Hãy thử cái này

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
Một câu trả lời tốt sẽ luôn có lời giải thích về những gì đã được thực hiện và tại sao nó được thực hiện theo cách như vậy, không chỉ cho OP mà còn cho khách truy cập tương lai của SO.
B001

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.