ปูต้นฉบับคีบลูกปัดที่ผ่านเงื่อนไขออกจากช่วงกลางของรางโดยเก็บลูกอื่นไว้

Rust Vec::extract_if: ดึงสมาชิกออกเฉพาะช่วงโดยยังเก็บลำดับที่เหลือ

พี่โชว์Engineering

งานคิวบางแบบต้อง “ย้ายเฉพาะรายการพร้อมทำ” ออกจากช่วงหนึ่งของ Vec และยังต้องได้รายการที่ย้ายไปใช้ต่อครับ Vec::extract_if คืน iterator ของสมาชิกที่ถูกนำออก ขณะที่สมาชิกที่ไม่ผ่านเงื่อนไขยังอยู่และรักษาลำดับเดิม

WHY: ต่างจาก retain ตรงที่เราอยากได้ของที่เอาออก

retain_mut เหมาะเมื่อสนใจเพียงสิ่งที่เหลือ ส่วน extract_if(range, predicate) ให้ทั้งสองฝั่ง: iterator ส่งสมาชิกที่ predicate คืน true ออกมา และ Vec เก็บรายการอื่นไว้ Range ช่วยจำกัดพื้นที่ตรวจ เช่นแตะเฉพาะหน้าต่างคิวรอบนี้โดยไม่สแกนทั้งก้อน

เหมือนพนักงานหยิบเฉพาะกล่องพร้อมส่งจากชั้นกลาง กล่องก่อนและหลังช่วงตรวจไม่ถูกเปิด แต่ถ้าหยิบออกจากกลางราง กล่องด้านหลังยังต้องเลื่อนมาปิดช่อง ต้นทุนจึงไม่หายไปเพียงเพราะ API ดูกระชับ

fn extract_ready(values: &mut Vec<i32>) -> Vec<i32> {
    values.extract_if(1..5, |value| *value % 2 == 0).collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn extracts_only_even_values_inside_the_range() {
        let mut values = vec![8, 1, 2, 3, 4, 6];
        let removed = extract_ready(&mut values);
        assert_eq!(removed, vec![2, 4]);
        assert_eq!(values, vec![8, 1, 3, 6]);
    }

    #[test]
    fn dropping_early_keeps_unvisited_items() {
        let mut values = vec![1, 2, 4, 6];
        let first = values.extract_if(.., |value| *value % 2 == 0).next();
        assert_eq!(first, Some(2));
        assert_eq!(values, vec![1, 4, 6]);
    }
}

จุดละเอียด: iterator นี้ทำงานแบบ lazy

การเรียก extract_if ยังไม่เดินทุกสมาชิก งานเกิดตอน consume iterator หากใช้ .next() ครั้งเดียวแล้วปล่อย iterator รายการที่ยังไม่ถูกเยี่ยมจะคงอยู่ใน Vec ตาม contract นี่ดีสำหรับ short-circuit แต่ทำให้โค้ดที่ตั้งใจ “ลบทุกตัว” ผิดได้เงียบ ๆ ควรใช้ .collect(), .for_each(drop) หรือวนจนหมดให้เจตนาชัด

Predicate รับ &mut T จึงแก้สมาชิกก่อนตัดสินได้ แต่ side effect แบบนั้นเพิ่มภาระ review โดยเฉพาะเมื่อ panic ระหว่างทาง สมาชิกที่ยังไม่ถูกตรวจจะคงอยู่ อย่าใช้ closure ที่แตะระบบภายนอกหรือพึ่งลำดับการเรียกหาก rollback สำคัญ

Recap

  • ใช้ extract_if เมื่ออยากได้ทั้งรายการที่ย้ายออกและ Vec ที่เหลือ
  • Range จำกัดสมาชิกที่ถูกพิจารณา ไม่ได้ลดต้นทุนเลื่อนท้ายเสมอ
  • Consume iterator ให้หมดเมื่อเจตนาคือลบครบทุกตัว
  • ใช้ retain_mut หากไม่ต้องการค่าที่ถูกนำออก