ประมวลผลชุด Item ด้วย Iterator
pattern iterator ให้คุณทำงานบางอย่างบนลำดับของ item ทีละตัว iterator รับผิดชอบ logic ของการ iterate ผ่านแต่ละ item และกำหนดเมื่อลำดับจบ เมื่อคุณใช้ iterator คุณไม่ต้อง reimplement logic นั้นเอง
ใน Rust iterator เป็น lazy หมายความว่าพวกมันไม่มีผลจนกว่าคุณจะ
เรียกเมธอดที่ consume iterator เพื่อใช้มันให้หมด ตัวอย่างเช่น โค้ดใน
Listing 13-10 สร้าง iterator ผ่าน item ใน vector v1 โดยเรียก
เมธอด iter ที่นิยามบน Vec<T> โค้ดนี้โดยตัวมันเองไม่ทำอะไรที่มี
ประโยชน์
fn main() {
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
}
iterator ถูกเก็บในตัวแปร v1_iter เมื่อเราสร้าง iterator แล้ว เรา
ใช้มันได้หลายวิธี ใน Listing 3-5 เรา iterate ผ่าน array โดยใช้ loop
for เพื่อ execute โค้ดบางอย่างบนแต่ละ item ของมัน เบื้องหลัง
นี่สร้างและจากนั้น consume iterator โดยปริยาย แต่เรามองข้ามว่ามัน
ทำงานอย่างไรจนถึงตอนนี้
ในตัวอย่างใน Listing 13-11 เราแยกการสร้าง iterator จากการใช้
iterator ใน loop for เมื่อ loop for ถูกเรียกโดยใช้ iterator ใน
v1_iter แต่ละ element ใน iterator ถูกใช้ใน iteration หนึ่งของ
loop ซึ่ง print ออกแต่ละค่า
fn main() {
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
for val in v1_iter {
println!("Got: {val}");
}
}
forในภาษาที่ไม่มี iterator ที่ standard library ของพวกมันให้มา คุณคงจะ เขียน functionality เดียวกันนี้โดยเริ่มตัวแปรที่ index 0 ใช้ตัวแปร นั้น index เข้า vector เพื่อรับค่า และเพิ่มค่าตัวแปรใน loop จนกว่า มันถึงจำนวนรวมของ item ใน vector
iterator จัดการ logic ทั้งหมดนั้นให้คุณ ตัดโค้ดที่ซ้ำที่คุณอาจทำพัง iterator ให้คุณยืดหยุ่นมากขึ้นในการใช้ logic เดียวกันกับลำดับหลาย ประเภทต่างกัน ไม่ใช่แค่โครงสร้างข้อมูลที่คุณ index เข้าได้ เช่น vector มาตรวจสอบว่า iterator ทำอย่างนั้นได้อย่างไร
Trait Iterator และเมธอด next
iterator ทั้งหมด implement trait ชื่อ Iterator ที่นิยามใน standard
library นิยามของ trait ดูแบบนี้:
#![allow(unused)]
fn main() {
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// methods with default implementations elided
}
}
สังเกตว่านิยามนี้ใช้ syntax ใหม่บางอย่าง — type Item และ
Self::Item ซึ่งกำลังนิยาม associated type กับ trait นี้ เราจะพูด
ถึง associated type อย่างลึกในบทที่ 20 ตอนนี้ ทั้งหมดที่คุณต้องรู้
คือโค้ดนี้บอกว่าการ implement trait Iterator ต้องการให้คุณนิยาม
type Item ด้วย และ type Item นี้ใช้ใน return type ของเมธอด
next อีกแง่หนึ่ง type Item จะเป็น type ที่ return จาก iterator
trait Iterator ต้องการให้ผู้ implement นิยามเพียงหนึ่งเมธอด —
เมธอด next ซึ่ง return หนึ่ง item ของ iterator ในแต่ละครั้ง
wrapped ใน Some และเมื่อ iteration จบ return None
เราเรียกเมธอด next บน iterator โดยตรงได้ Listing 13-12 สาธิตค่า
ที่ return จากการเรียก next ซ้ำบน iterator ที่สร้างจาก vector
#[cfg(test)]
mod tests {
#[test]
fn iterator_demonstration() {
let v1 = vec![1, 2, 3];
let mut v1_iter = v1.iter();
assert_eq!(v1_iter.next(), Some(&1));
assert_eq!(v1_iter.next(), Some(&2));
assert_eq!(v1_iter.next(), Some(&3));
assert_eq!(v1_iter.next(), None);
}
}
next บน iteratorสังเกตว่าเราต้องทำให้ v1_iter เป็น mutable — การเรียกเมธอด next
บน iterator เปลี่ยน state ภายในที่ iterator ใช้เพื่อตามว่ามันอยู่ที่
ไหนในลำดับ อีกแง่หนึ่ง โค้ดนี้ consume หรือใช้ iterator หมด แต่ละ
การเรียก next กิน item หนึ่งจาก iterator เราไม่ต้องทำให้
v1_iter เป็น mutable เมื่อเราใช้ loop for เพราะ loop รับ
ownership ของ v1_iter และทำให้มัน mutable เบื้องหลัง
สังเกตด้วยว่าค่าที่เราได้จากการเรียก next เป็น immutable reference
ของค่าใน vector เมธอด iter สร้าง iterator ผ่าน immutable reference
ถ้าเราต้องการสร้าง iterator ที่รับ ownership ของ v1 และ return
ค่าที่ own เราเรียก into_iter แทน iter ได้ ในทำนองเดียวกัน ถ้า
เราต้องการ iterate ผ่าน mutable reference เราเรียก iter_mut แทน
iter ได้
เมธอดที่ Consume Iterator
trait Iterator มีหลายเมธอดต่างกันที่มี implementation เริ่มต้นที่
standard library ให้มา — คุณค้นหาเกี่ยวกับเมธอดเหล่านี้ได้โดยดูใน
API documentation ของ standard library สำหรับ trait Iterator
บางเมธอดเหล่านี้เรียกเมธอด next ในนิยามของพวกมัน ซึ่งเป็นเหตุผล
ที่คุณต้อง implement เมธอด next เมื่อ implement trait Iterator
เมธอดที่เรียก next ถูกเรียก consuming adapter เพราะการเรียก
พวกมันใช้ iterator หมด ตัวอย่างหนึ่งคือเมธอด sum ซึ่งรับ
ownership ของ iterator และ iterate ผ่าน item โดยเรียก next ซ้ำ
ดังนั้น consume iterator เมื่อมัน iterate ผ่าน มันเพิ่มแต่ละ item
ให้ running total และ return total เมื่อ iteration เสร็จ Listing
13-13 มีเทสแสดงการใช้เมธอด sum
#[cfg(test)]
mod tests {
#[test]
fn iterator_sum() {
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
let total: i32 = v1_iter.sum();
assert_eq!(total, 6);
}
}
sum เพื่อรับ total ของ item ทั้งหมดใน iteratorเราไม่ได้รับอนุญาตให้ใช้ v1_iter หลังการเรียก sum เพราะ sum
รับ ownership ของ iterator ที่เราเรียกมันบน
เมธอดที่ผลิต Iterator อื่น
Iterator adapter คือเมธอดที่นิยามบน trait Iterator ที่ไม่
consume iterator แทน พวกมันผลิต iterator ต่างกันโดยเปลี่ยนแง่มุม
บางอย่างของ iterator เดิม
Listing 13-14 แสดงตัวอย่างของการเรียก iterator adapter เมธอด map
ซึ่งรับ closure ที่จะเรียกบนแต่ละ item ขณะที่ item ถูก iterate
เมธอด map return iterator ใหม่ที่ผลิต item ที่ถูกแก้ closure ที่นี่
สร้าง iterator ใหม่ที่แต่ละ item จาก vector จะถูกเพิ่มขึ้น 1
fn main() {
let v1: Vec<i32> = vec![1, 2, 3];
v1.iter().map(|x| x + 1);
}
map เพื่อสร้าง iterator ใหม่อย่างไรก็ตาม โค้ดนี้สร้าง warning:
$ cargo run
Compiling iterators v0.1.0 (file:///projects/iterators)
warning: unused `Map` that must be used
--> src/main.rs:4:5
|
4 | v1.iter().map(|x| x + 1);
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: iterators are lazy and do nothing unless consumed
= note: `#[warn(unused_must_use)]` on by default
help: use `let _ = ...` to ignore the resulting value
|
4 | let _ = v1.iter().map(|x| x + 1);
| +++++++
warning: `iterators` (bin "iterators") generated 1 warning
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.47s
Running `target/debug/iterators`
โค้ดใน Listing 13-14 ไม่ทำอะไร — closure ที่เราระบุไม่เคยถูกเรียก warning เตือนเราทำไม — iterator adapter เป็น lazy และเราต้อง consume iterator ที่นี่
เพื่อแก้ warning นี้และ consume iterator เราจะใช้เมธอด collect
ที่เราใช้กับ env::args ใน Listing 12-1 เมธอดนี้ consume iterator
และ collect ค่าที่ได้เข้า collection data type
ใน Listing 13-15 เรา collect ผลของการ iterate ผ่าน iterator ที่
return จากการเรียก map เข้า vector vector นี้จะลงเอยที่บรรจุแต่ละ
item จาก vector เดิม เพิ่มขึ้น 1
fn main() {
let v1: Vec<i32> = vec![1, 2, 3];
let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();
assert_eq!(v2, vec![2, 3, 4]);
}
map เพื่อสร้าง iterator ใหม่ และจากนั้นเรียกเมธอด collect เพื่อ consume iterator ใหม่และสร้าง vectorเพราะ map รับ closure เราระบุ operation ใดก็ได้ที่เราต้องการทำบน
แต่ละ item นี่เป็นตัวอย่างยอดเยี่ยมของวิธีที่ closure ให้คุณกำหนด
พฤติกรรมเองในขณะที่ใช้พฤติกรรม iteration ที่ trait Iterator ให้มา
ซ้ำ
คุณ chain การเรียก iterator adapter หลายตัวเพื่อทำการกระทำที่ซับซ้อน ในแบบที่อ่านได้ แต่เพราะ iterator ทั้งหมดเป็น lazy คุณต้องเรียกหนึ่ง ในเมธอด consuming adapter เพื่อรับผลจากการเรียก iterator adapter
Closure ที่จับ Environment ของพวกมัน
iterator adapter หลายตัวรับ closure เป็นอาร์กิวเมนต์ และโดยทั่วไป closure ที่เราจะระบุเป็นอาร์กิวเมนต์ให้ iterator adapter จะเป็น closure ที่จับ environment ของพวกมัน
สำหรับตัวอย่างนี้ เราจะใช้เมธอด filter ที่รับ closure closure รับ
item จาก iterator และ return bool ถ้า closure return true ค่า
จะถูกรวมใน iteration ที่ผลิตโดย filter ถ้า closure return false
ค่าจะไม่ถูกรวม
ใน Listing 13-16 เราใช้ filter กับ closure ที่จับตัวแปร
shoe_size จาก environment ของมันเพื่อ iterate ผ่าน collection ของ
instance struct Shoe มันจะ return เฉพาะรองเท้าที่เป็นขนาดที่ระบุ
#[derive(PartialEq, Debug)]
struct Shoe {
size: u32,
style: String,
}
fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
shoes.into_iter().filter(|s| s.size == shoe_size).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn filters_by_size() {
let shoes = vec![
Shoe {
size: 10,
style: String::from("sneaker"),
},
Shoe {
size: 13,
style: String::from("sandal"),
},
Shoe {
size: 10,
style: String::from("boot"),
},
];
let in_my_size = shoes_in_size(shoes, 10);
assert_eq!(
in_my_size,
vec![
Shoe {
size: 10,
style: String::from("sneaker")
},
Shoe {
size: 10,
style: String::from("boot")
},
]
);
}
}
filter กับ closure ที่จับ shoe_sizeฟังก์ชัน shoes_in_size รับ ownership ของ vector ของรองเท้าและขนาด
รองเท้าเป็น parameter มัน return vector ที่บรรจุเฉพาะรองเท้าของขนาด
ที่ระบุ
ใน body ของ shoes_in_size เราเรียก into_iter เพื่อสร้าง iterator
ที่รับ ownership ของ vector จากนั้น เราเรียก filter เพื่อ adapt
iterator นั้นเป็น iterator ใหม่ที่บรรจุเฉพาะ element ที่ closure
return true
closure จับ parameter shoe_size จาก environment และเปรียบเทียบ
ค่ากับขนาดของแต่ละรองเท้า เก็บเฉพาะรองเท้าของขนาดที่ระบุ สุดท้าย
การเรียก collect รวมค่าที่ return โดย iterator ที่ adapt เข้า
vector ที่ return โดยฟังก์ชัน
เทสแสดงว่าเมื่อเราเรียก shoes_in_size เราได้คืนเฉพาะรองเท้าที่
มีขนาดเดียวกับค่าที่เราระบุ