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
}
