ปูสีส้มแดงต้นฉบับกำลังปรับชิ้นงานบนสายพานแล้วคัดชิ้นที่ไม่ผ่านลงถาดข้างโต๊ะ

Rust Vec::retain_mut: แก้ค่าและกรองสมาชิกในรอบเดียวโดยรักษาลำดับ

พี่โชว์Engineering

ถ้าต้อง normalize ค่าทุกตัวแล้วคัดของเสียออก เรามักเขียน iter_mut() รอบหนึ่งและ retain() อีกรอบครับ Vec::retain_mut ส่ง &mut T เข้า predicate จึงแก้ค่าสมาชิกก่อนตัดสินใจเก็บได้ในรอบเดียว และรักษาลำดับของตัวที่เหลือ

fn normalize_positive(values: &mut Vec<i32>) {
    values.retain_mut(|value| {
        *value *= 2;
        *value > 0
    });
}

fn main() {
    let mut values = vec![-2, 1, 3, 0];
    normalize_positive(&mut values);
    assert_eq!(values, vec![2, 6]);
    println!("retain_mut checks passed");
}

Closure ถูกเรียกตามลำดับเดิมหนึ่งครั้งต่อสมาชิก การคืน true คือเก็บค่าที่อาจถูกแก้แล้ว ส่วน false คือทิ้ง เหมือนช่างหยิบสินค้าบนสายพาน ปรับฉลาก แล้วตัดสินใจวางกลับหรือใส่กล่อง reject ในมือเดียว

ข้อแลกเปลี่ยน

วิธีนี้ใช้พื้นที่เพิ่ม O(1) แต่การลบสมาชิกจำนวนมากจาก vector ต้องย้ายสมาชิกที่รอดให้ชิดกัน ต้นทุนรวมยังเป็น O(n) หากต้องสร้างผลลัพธ์ชนิดใหม่หรือเก็บค่าที่ถูกคัดออก ให้ใช้ drain, partition หรือ iterator ที่ collect แล้วสื่อ intent ได้ดีกว่า

อย่าใส่ side effect ที่ย้อนกลับไม่ได้ใน predicate เพราะหาก closure panic vector จะยังอยู่ในสถานะที่ปลอดภัยตามชนิด แต่ข้อมูลบางตัวอาจถูกแก้และบางตัวอาจถูกลบแล้ว การทำ normalization ควรเป็น deterministic และทดสอบ input ว่าง, ทุกตัวถูกลบ และทุกตัวผ่าน

Recap

  • ใช้ retain_mut เมื่อแก้และกรอง Vec เดิมจริง ๆ
  • คืน true เพื่อเก็บค่าหลังแก้
  • ใช้ทางเลือกอื่นเมื่อต้องเก็บของที่ถูกตัดหรือเปลี่ยนชนิดผลลัพธ์
  • หลีกเลี่ยง side effect ภายนอกใน predicate