ค้นหาเว็บไซต์

javascript - การวนลูปผ่านอาร์เรย์และลบรายการโดยไม่ทำให้ลูป for หยุด


ฉันมีลูป for ดังต่อไปนี้ และเมื่อฉันใช้ splice() เพื่อลบไอเท็มออกไป ฉันก็จะพบว่า 'seconds' ไม่ถูกกำหนดค่า ฉันสามารถตรวจสอบว่ามันไม่ได้ถูกกำหนดค่า แต่ฉันคิดว่าน่าจะมีวิธีที่สวยงามกว่านี้ ความต้องการคือเพียงแค่ลบไอเท็มออกและดำเนินการต่อ

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

คำตอบ #1

อาร์เรย์จะถูกจัดเรียงดัชนีใหม่เมื่อคุณใช้ .splice() ซึ่งหมายความว่าคุณจะข้ามดัชนีหนึ่งเมื่อมีการลบออก และ .length ที่เก็บไว้ของคุณจะล้าสมัย

ในการแก้ไข คุณจะต้องลดค่า i หลังจาก .splice() หรือเพียงแค่วนซ้ำย้อนกลับ...

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

ด้วยวิธีนี้ การทำรีอินเด็กซ์จะไม่ส่งผลกระทบต่อรายการถัดไปในการวนซ้ำ เนื่องจากการจัดทำดัชนีมีผลเฉพาะกับรายการตั้งแต่จุดปัจจุบันไปจนถึงส่วนท้ายของอาร์เรย์ และรายการถัดไปในการวนซ้ำมีค่าต่ำกว่าจุดปัจจุบัน

คำตอบ #2

นี่เป็นปัญหาที่ค่อนข้างพบบ่อย วิธีแก้คือการวนลูปย้อนกลับ:

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

ไม่สำคัญหรอกว่าคุณจะเอามันออกจากปลายเพราะดัชนีจะยังคงอยู่เมื่อคุณย้อนกลับไป

คำตอบ #3

คำนวณความยาวใหม่ในทุกครั้งที่ผ่านลูป แทนที่จะทำเพียงตอนเริ่มต้น เช่น:

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

ด้วยวิธีนั้นคุณจะไม่เกินขอบเขต

แก้ไข: เพิ่มการลดค่าลงในคำสั่ง if.

คำตอบ #4

แม้ว่าคำถามของคุณจะเกี่ยวกับการลบองค์ประกอบจาก อาร์เรย์ที่กำลังถูกวนลูป และไม่เกี่ยวกับการลบองค์ประกอบ (พร้อมกับการประมวลผลอื่นๆ) อย่างมีประสิทธิภาพ แต่ผมคิดว่าควรพิจารณาใหม่ถ้าอยู่ในสถานการณ์ที่คล้ายกัน

ความซับซ้อนเชิงอัลกอริทึมของวิธีการนี้คือ O(n^2) เนื่องจากฟังก์ชัน splice และลูป for ทั้งสองวนซ้ำผ่านอาร์เรย์ (ฟังก์ชัน splice จะเลื่อนทุกองค์ประกอบของอาร์เรย์ในกรณีที่เลวร้ายที่สุด) แทนที่จะทำเช่นนั้น คุณสามารถเพียงแค่ push องค์ประกอบที่ต้องการไปยังอาร์เรย์ใหม่ จากนั้นก็เพียงกำหนดอาร์เรย์นั้นไปยังตัวแปรที่ต้องการ (ซึ่งเพิ่งถูกวนซ้ำไปแล้ว)

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;

ตั้งแต่ ES2015 เป็นต้นมา เราสามารถใช้ Array.prototype.filter เพื่อให้ทุกอย่างอยู่ในบรรทัดเดียวได้:

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

คำตอบ #5

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

คำตอบ #6

ถ้าคุณกำลังใช้ ES6+ - ทำไมไม่ใช้วิธี Array.filter ล่ะ?

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

โปรดทราบว่าการแก้ไของค์ประกอบของอาร์เรย์ระหว่างการวนซ้ำด้วย filter ใช้ได้เฉพาะกับวัตถุและจะไม่ทำงานกับอาร์เรย์ที่มีค่าพื้นฐาน

คำตอบ #7

นี่คือวิธีแก้ปัญหาแบบเวลเชิงเส้นที่ง่ายต่อปัญหาแบบเวลเชิงเส้นที่ง่ายนี้

เมื่อฉันรันโค้ดตัวอย่างนี้ โดยมี n=1 ล้าน การเรียกใช้ filterInPlace() แต่ละครั้งใช้เวลา 0.013 ถึง 0.016 วินาที โซลูชันเชิงกำลังสอง (เช่น คำตอบที่ถูกยอมรับ) จะใช้เวลามากกว่าหนึ่งล้านเท่า หรือประมาณนั้น

// 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, auction => --auction.seconds >= 0);
console.log("array length should be "+(2*n)+": ", Auction.auctions.length);
filterInPlace(Auction.auctions, auction => --auction.seconds >= 0);
console.log("array length should be "+n+": ", Auction.auctions.length);
filterInPlace(Auction.auctions, auction => --auction.seconds >= 0);
console.log("array length should be 0: ", Auction.auctions.length);

โปรดทราบว่าสิ่งนี้จะปรับเปลี่ยนอาเรย์ต้นฉบับโดยตรงแทนที่จะสร้างอาเรย์ใหม่ การทำเช่นนี้โดยตรงอาจเป็นประโยชน์ เช่น ในกรณีที่อาเรย์เป็นคอขวดของหน่วยความจำของโปรแกรม ในกรณีนั้น คุณไม่ต้องการสร้างอาเรย์อีกชุดหนึ่งที่มีขนาดเท่ากัน แม้เพียงชั่วคราว

คำตอบ #8

ลูป for ปกติคุ้นเคยกับฉันมากกว่า ฉันเพียงแค่ต้องลดค่า index ทุกครั้งที่ฉันลบรายการออกจากอาร์เรย์

//5 trues , 5 falses
var arr1 = [false, false, true, true, false, true, false, true, true, false];

//remove falses from array
for (var i = 0; i < arr1.length; i++){
    if (arr1[i] === false){
        arr1.splice(i, 1);
        i--;// decrement index if item is removed
    }
}
console.log(arr1);// should be 5 trues

คำตอบ #9

อีกวิธีง่าย ๆ ในการประมวลผลองค์ประกอบของอาร์เรย์เพียงครั้งเดียว:

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
}

บทความที่เกี่ยวข้อง