Rust Vec::extract_if: ดึงสมาชิกออกเฉพาะช่วงโดยยังเก็บลำดับที่เหลือ
งานคิวบางแบบต้อง “ย้ายเฉพาะรายการพร้อมทำ” ออกจากช่วงหนึ่งของ 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หากไม่ต้องการค่าที่ถูกนำออก