Iterator trong Rust
Iterator là bất cứ thứ gì có thể tạo ra một dãy giá trị, từng giá trị một. Thật ra bạn đã dùng chúng từ trang Vòng lặp for mà có thể chưa gọi tên: mỗi vòng for chạy qua một collection đều có một iterator làm việc phía sau.
Lấy một iterator
Phần tiêu đề “Lấy một iterator”fn main() { let numbers = vec![1, 2, 3]; let mut iter = numbers.iter();
println!("{:?}", iter.next()); // Some(1) println!("{:?}", iter.next()); // Some(2) println!("{:?}", iter.next()); // Some(3) println!("{:?}", iter.next()); // None}Kết quả:
Some(1)Some(2)Some(3)None.next() trả về một Option: Some(value) cho mỗi phần tử, và None khi đã hết. Một vòng for thực chất chỉ là gọi .next() lặp đi lặp lại phía sau cho tới khi nhận được None.
map: biến đổi từng phần tử
Phần tiêu đề “map: biến đổi từng phần tử”.map() nhận một closure và áp dụng nó cho từng phần tử, tạo ra một iterator mới:
fn main() { let numbers = vec![1, 2, 3, 4, 5]; let doubled: Vec<i32> = numbers.iter().map(|n| n * 2).collect();
println!("{:?}", doubled);}Kết quả:
[2, 4, 6, 8, 10].collect() là thứ biến iterator trở lại thành một collection cụ thể (ở đây là Vec<i32>). Không có nó, bạn chỉ có một iterator nằm đó, chưa hề được chạy.
filter: chỉ giữ lại một số phần tử
Phần tiêu đề “filter: chỉ giữ lại một số phần tử”fn main() { let numbers = vec![1, 2, 3, 4, 5, 6]; let evens: Vec<&i32> = numbers.iter().filter(|n| **n % 2 == 0).collect();
println!("{:?}", evens);}Kết quả:
[2, 4, 6]Hai dấu sao trong **n trông hơi lạ. .filter() đưa cho closure một tham chiếu tới một tham chiếu (&&i32), nên phải giải tham chiếu hai lần mới so sánh được với một số bình thường. Đây là một chi tiết nhỏ hay gặp; bạn chưa cần hiểu hết lời giải thích lúc này.
Nối map và filter với nhau
Phần tiêu đề “Nối map và filter với nhau”Đây là lúc việc nối các phương thức iterator bắt đầu phát huy tác dụng: bạn có thể ghép nhiều bước thành một chuỗi xử lý dễ đọc:
fn main() { let numbers = vec![1, 2, 3, 4, 5, 6];
let result: Vec<i32> = numbers .iter() .filter(|n| **n % 2 == 0) // giữ các số chẵn .map(|n| n * 10) // nhân mỗi số với 10 .collect();
println!("{:?}", result);}Kết quả:
[20, 40, 60]Hãy so với cách viết cùng logic đó bằng một vòng for thủ công và một Vec rỗng để push vào. Cách đó không sai, nhưng phiên bản nối chuỗi đọc gần với câu “lọc, rồi nhân, rồi gom lại”, đúng như các bước bạn đang mô tả.
Vài phương thức thông dụng khác
Phần tiêu đề “Vài phương thức thông dụng khác”fn main() { let numbers = vec![1, 2, 3, 4, 5];
let total: i32 = numbers.iter().sum(); let count = numbers.iter().count(); let has_even = numbers.iter().any(|n| n % 2 == 0);
println!("tổng: {total}, số phần tử: {count}, có số chẵn: {has_even}");}Kết quả:
tổng: 15, số phần tử: 5, có số chẵn: true.sum() cộng tất cả lại, .count() cho bạn biết có bao nhiêu phần tử, còn .any() trả lời câu hỏi “có ít nhất một phần tử khớp không?”. Đây chỉ là vài phương thức mà iterator nào cũng có. Còn rất nhiều nữa, như enumerate, zip, take và rev. Ở trang Iterator tự định nghĩa, bạn sẽ tự viết một iterator và thấy nó nhận được tất cả các phương thức này miễn phí.
Luyện tập
Phần tiêu đề “Luyện tập”1. Tổng bình phương các số lẻ
Phần tiêu đề “1. Tổng bình phương các số lẻ”Lấy các số từ 1 đến 10, chỉ giữ số lẻ, bình phương từng số rồi cộng tất cả lại. Hãy làm trong một chuỗi iterator duy nhất, không dùng biến mut nào. Gợi ý: nối .filter(), .map() và .sum(), rồi ghi kiểu cho kết quả, ví dụ let total: i32.
Xem lời giải
fn main() { let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let total: i32 = numbers .iter() .filter(|n| **n % 2 == 1) // giữ 1, 3, 5, 7, 9 .map(|n| n * n) // 1, 9, 25, 49, 81 .sum();
println!("tổng bình phương các số lẻ: {total}");}Kết quả:
tổng bình phương các số lẻ: 165Rust cần : i32 trên total vì .sum() có thể cộng dồn thành nhiều kiểu số khác nhau, và nó không đoán được bạn muốn kiểu nào.
2. Viết hoa các từ dài
Phần tiêu đề “2. Viết hoa các từ dài”Bắt đầu với vec!["the", "quick", "brown", "fox", "jumps"]. Tạo một Vec<String> mới chỉ chứa các từ dài hơn 3 chữ cái, ở dạng viết hoa, rồi in ra bằng {:?}. Gợi ý: .filter(|w| w.len() > 3) giữ lại các từ dài, còn .to_uppercase() biến một &str thành một String viết hoa. (Các từ được giữ bằng tiếng Anh vì .len() đếm byte, và chữ tiếng Việt có dấu chiếm nhiều byte hơn.)
Xem lời giải
fn main() { let words = vec!["the", "quick", "brown", "fox", "jumps"];
let loud: Vec<String> = words .iter() .filter(|w| w.len() > 3) .map(|w| w.to_uppercase()) .collect();
println!("{:?}", loud);}Kết quả:
["QUICK", "BROWN", "JUMPS"]Bản thân words không hề thay đổi. .iter() chỉ mượn nó, nên bạn vẫn dùng được words sau chuỗi xử lý.
Tóm tắt
Phần tiêu đề “Tóm tắt”- Iterator tạo ra từng giá trị một qua
.next(), hàm này trả về mộtOption. - Vòng lặp
forthực chất dùng iterator phía sau. .map()biến đổi từng phần tử;.filter()chỉ giữ những phần tử thỏa điều kiện;.collect()biến kết quả trở lại thành một collection cụ thể.- Nối các phương thức iterator với nhau thường rõ ràng hơn một vòng lặp thủ công với một biến tích lũy khả biến.
Kiểm tra nhanh
Phần tiêu đề “Kiểm tra nhanh”Kiểm tra nhanh
1. Gọi .next() trên một iterator thì nhận được gì?
2. .collect() làm gì ở cuối một chuỗi như .iter().map(...).collect()?
3. Trong .filter(|n| **n % 2 == 0), vì sao n bị giải tham chiếu hai lần?
4. Thứ gì giúp vòng for chạy qua một collection?
5. .filter() giữ lại gì trong iterator kết quả?
Điểm: 0 / 5
Đọc thêm
Phần tiêu đề “Đọc thêm”- Processing a Series of Items with Iterators — The Rust Book (tiếng Anh): phần giới thiệu chính thức về
next,map,filtervàcollect, với nhiều ví dụ hơn. - Comparing Performance: Loops vs. Iterators — The Rust Book (tiếng Anh): vì sao chuỗi iterator chạy nhanh không kém một vòng lặp viết tay.
- Trait
Iterator— tài liệu thư viện chuẩn (tiếng Anh): danh sách đầy đủ các phương thức của iterator, mỗi cái có một ví dụ nhỏ. Rất đáng để lướt qua. - Iterators — Rust by Example (tiếng Anh): một ví dụ ngắn, chạy được, xây dựng iterator Fibonacci.