วนรอบผ่านอาร์เรย์และนำรายการออกโดยไม่ทำลายให้วนซ้ำ


462

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

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
นอกเหนือจากการวนซ้ำไปข้างหลังและปรับความยาวแล้วคุณยังสามารถใส่สมาชิกที่คุณต้องการในอาร์เรย์ใหม่ได้อีกด้วย
RobG

2
ทำไมคุณพูดAuction.auctions[i]['seconds']--แทนauction.seconds--?
Don Hatch

คุณอาจต้องการดูฟังก์ชัน. shift () ที่กำหนดไว้ล่วงหน้า
raku

คำตอบ:


856

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

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

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

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


151

นี่เป็นปัญหาที่พบได้บ่อย การแก้ปัญหาคือการวนซ้ำไปข้างหลัง:

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

ไม่สำคัญว่าคุณกำลังจะดึงพวกเขาออกจากท้ายเพราะดัชนีจะถูกเก็บรักษาไว้เมื่อคุณย้อนกลับ


48

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

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


32

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

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

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


10

หากคุณเป็นผู้ใช้ ES6 + - ทำไมไม่ใช้วิธี Array.filter?

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

โปรดทราบว่าการแก้ไของค์ประกอบอาร์เรย์ในระหว่างการวนซ้ำตัวกรองจะทำงานกับวัตถุเท่านั้นและจะไม่ทำงานสำหรับอาร์เรย์ของค่าดั้งเดิม


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
}

8

นี่คืออีกตัวอย่างสำหรับการใช้งานรอยต่อที่เหมาะสม ตัวอย่างนี้กำลังจะลบ 'attribute' ออกจาก 'array'

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

8

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

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

เมื่อฉันเรียกใช้ส่วนย่อยนี้ด้วย n = 1 ล้านการเรียกแต่ละครั้งเพื่อ filterInPlace () ใช้เวลา. 013 ถึง. 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, 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)

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


ฉันไม่เคยรู้เลยว่าคุณสามารถกำหนดความยาวของอาเรย์ได้!
ไมเคิล

ฉันไม่ทราบว่าArray.splice(i,1)จะสร้างอินสแตนซ์อาร์เรย์ใหม่ทุกครั้ง ฉันละอายใจมาก
dehart

2
@dehart Ha, ดี :-) ที่จริงแล้วมันไม่ได้สร้างอินสแตนซ์อาร์เรย์ใหม่ทุกครั้ง แต่มันจะต้องชนทุกรายการที่ดัชนีมีค่ามากกว่าฉันลงซึ่งอยู่ที่การกระแทกเฉลี่ย n / 2
Don Hatch

1

มีคำตอบที่ยอดเยี่ยมมากมายในกระทู้นี้แล้ว อย่างไรก็ตามฉันต้องการแบ่งปันประสบการณ์ของฉันเมื่อฉันพยายามที่จะแก้ปัญหา "ลบองค์ประกอบที่ n จากอาร์เรย์" ในบริบท ES5

อาร์เรย์ JavaScript มีวิธีการที่แตกต่างกันในการเพิ่ม / ลบองค์ประกอบตั้งแต่เริ่มต้นหรือสิ้นสุด เหล่านี้คือ:

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 

เป็นหลักไม่มีวิธีการข้างต้นสามารถใช้โดยตรงเพื่อลบองค์ประกอบที่ n จากอาร์เรย์

ความจริงที่น่าสังเกตคือนี่เป็นสิ่งที่ตรงกันข้ามกับการใช้ตัวจาวา iterator ซึ่งเป็นไปได้ที่จะลบองค์ประกอบที่ n สำหรับคอลเลกชันในขณะที่วนซ้ำ

สิ่งนี้จะทำให้เรามีวิธีการเรียงลำดับเดียวที่Array.spliceจะทำการกำจัดองค์ประกอบที่ n (มีสิ่งอื่น ๆ ที่คุณสามารถทำได้ด้วยวิธีการเหล่านี้เช่นกัน แต่ในบริบทของคำถามนี้ฉันมุ่งเน้นไปที่การกำจัดองค์ประกอบ):

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

นี่คือรหัสที่คัดลอกมาจากคำตอบดั้งเดิม (พร้อมความคิดเห็น):

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

Array.sliceอีกวิธีที่น่าสังเกตคือ อย่างไรก็ตามประเภทการคืนของวิธีนี้คือองค์ประกอบที่ถูกลบออก นอกจากนี้ยังไม่ได้ปรับเปลี่ยนอาร์เรย์เดิม แก้ไขข้อมูลโค้ดดังต่อไปนี้:

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

ต้องบอกว่าเรายังคงสามารถใช้Array.sliceเพื่อลบองค์ประกอบที่ n ดังที่แสดงด้านล่าง อย่างไรก็ตามมันเป็นรหัสที่มากขึ้น (ไม่มีประสิทธิภาพ)

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.sliceวิธีเป็นสิ่งสำคัญมากเพื่อให้บรรลุเปลี่ยนไม่ได้ในการเขียนโปรแกรมการทำงานàลา Redux


โปรดทราบว่ารหัสเพิ่มเติมไม่ควรเป็นการวัดประสิทธิภาพของรหัส
kano

0

ลองถ่ายทอดอาร์เรย์ไปยัง newArray เมื่อวนลูป:

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

ตัวอย่างสองข้อที่ใช้งานได้:

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


-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
คำตอบที่ดีมักจะมีคำอธิบายของสิ่งที่ทำและทำไมมันก็ทำในลักษณะดังกล่าวไม่เพียง แต่สำหรับ OP แต่สำหรับผู้เข้าชมในอนาคตที่จะ SO
B001 ᛦ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.