Iterators
Links:
- https://doc.rust-lang.org/book/ch13-02-iterators.html
- https://dev.to/martcpp/understanding-map-vs-flatmap-in-rust-with-a-simple-analogy-408g
Итераторы
Итераторы позволяют выполнять действия по очереди над цепочкой данных. Итератор берёт каждый объект цепочки и проверяет, не последний или он. Итераторы в Rust - ленивые, т.е не отрабатывают, пока не будет вызван метод, который их поглощает.
iter()
Возвращает Iterator<Item = &T>, забирает на себя массив без изменений (immutable), поэтому исходный массив далее доступен для других действий. Применять для задач чтения.
let v1 = [1, 2, 3];
let v1_iter = v1.iter();
for val in v1_iter { // iterator consume by for cycle
println!("Got: {val}");
}
println!("{v1:?}"); // v1 доступен после итератора
into_iter()
Возвращает Iterator<Item = T>, делает move массиву, после применения массив использовать нельзя. Применять для задач изменений с исходным массивом.
let v1 = [1, 2, 3];
for num in v1.into_iter() {
println!("{}", num); } // num is i32 (owned value)
// println!("{v1:?}"); // ❌ ошибка, v1 перемещён
Когда ты пишешь for x in коллекция, Rust автоматически вызывает into_iter(), потому что это “по умолчанию” забирает коллекцию. Если хочешь оставить коллекцию, явно используй for x in коллекция.iter().
iter_mut()
Возвращает Iterator<Item = &mut T>, забирает на себя массив с возможностью его менять прямо на месте. Исходный массив далее доступен для других действий.
let mut vec = vec![1, 2, 3];
// Создает iterator по mutable rссылкам (&mut T)
for num in vec.iter_mut() {
*num *= 2; } // можно менять элементы
println!("Modified: {:?}", vec); // [2, 4, 6]
Цикл for с iter(), into_iter(), iter_mut()
Синтаксический сахар:
let vec = vec![1, 2, 3];
for x in vec.iter() { /* x: &i32 */ }
for x in &vec { /* x: &i32 */ } // равно iter()
for x in vec.into_iter() { /* x: i32 */ }
for x in vec { /* x: i32 */ } // равно into_iter()
for x in vec.iter_mut() { /* x: &mut i32 */ }
for x in &mut vec { /* x: &mut i32 */ } // равно iter_mut()
| Характеристика | iter |
into_iter |
iter_mut |
|---|---|---|---|
| Что возвращает | Ссылки (<T>) |
Сами значения (T) |
Изменяемые ссылки (<mut T>) |
| Можно ли менять элементы | Нет, только смотреть | Да, но коллекция уже твоя | Да, через ссылки |
| Что с коллекцией | Остаётся живой | “Исчезает” (перемещается) | Остаётся живой |
| Тип итератора | Итератор по ссылкам | Итератор по значениям | Итератор по изменяемым ссылкам |
| Когда использовать | Хочу посмотреть элементы | Хочу забрать элементы | Хочу изменить элементы на месте |
Требуется ли mut для коллекции |
Нет | Нет (но коллекция уходит) | Да, коллекция должна быть mut |
| Пример кода | for x in vec.iter() |
for x in vec.into_iter() |
for x in vec.iter_mut() |
| Пример результата | x — ссылка, vec жив |
x — значение, vec мёртв |
x — изменяемая ссылка, vec жив |
repeat, repeat_n, take, skip
repeat - создаёт строку, повторяя заданный символ N раз (N as usize).
Например, вывести квадрат из символов “+” размера n (через клонирование):
fn generate_square(n: i32) -> String {
vec!["+".repeat(n as usize); n as usize].join("\n") }repeat_n - создаёт итератор, который возвращает заданный элемент N раз (N as usize) без клонирования (в отличие от repeat):
fn generate_square2(n: i32) -> String {
std::iter::repeat_n(
std::iter::repeat_n("+", n as usize).collect::<String>(),
n as usize,
)
.collect::<Vec<_>>()
.join("\n") }take - возвращает первые N элементов итератора (N as usize), если итератор не исчерпает себя раньше:
fn generate_square3(n: i32) -> String {
std::iter::repeat(std::iter::repeat("+").take(n as usize).collect::<String>()).take(n as usize).collect::<Vec<_>>().join("\n") }take часто используют с бесконечным итератором для задания ему конечного числа итераций:
let mut iter = (0..).take(3);
assert_eq!(iter.next(), Some(0));
assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), None);skip(n) создаёт новый итератор, в котором пропускает заданное число n элементов исходного итератора, а остальные возвращает. С помощью него удобно обрабатывать данные, пропустив заголовок, либо параметры введённой команды с пропуском имени самой команды:
use std::env;
fn main() {
// env::args() = итератор на Strings с аргументами.
// Вызываем skip(1) для пропуска пути к команде.
let arguments = env::args().skip(1);
// Клонируем clone() для проверки количества аргументом
// сохраняя исходный итератор
if arguments.clone().count() == 0 {
println!("No arguments provided other than the program name.");
} else {
println!("Processing arguments:");
for arg in arguments {
println!(" Argument: {}", arg);
}}}Вместе take() и skip() комбинируются, чтобы получить средние значения в массиве:
let data = [10, 20, 30, 40, 50, 60, 70, 80];
let iter = data.iter();
let middle_elements: Vec<_> = iter
.skip(2) // пропуск 2 элементов
.take(3) // взять 3 элемента = 30,40,50
.collect(); // поместить результат в Vec.
println!("Middle elements: {:?}", middle_elements);Отличие .map() и .flat_map()
Обе функции раскрывают итератор вектора, однако, с разным результатом:
fn main() {
let numbers = vec![1, 2, 3];
// Using `map()`
let mapped: Vec<Vec<i32>> = numbers.iter().map(|&n| vec![n, n * 10]).collect();
println!("{:?}", mapped); // [[1, 10], [2, 20], [3, 30]]
// Using `flat_map()`
let flat_mapped: Vec<i32> = numbers.iter().flat_map(|&n| vec![n, n * 10]).collect();
println!("{:?}", flat_mapped); // [1, 10, 2, 20, 3, 30]
}.map() - сохраняет структуру в итоговом результате .flat_map() - убирает лишние структуры
counts()
Считает число объектов в итераторе и возвращает как HashMap.
let text = "hello world".to_string();
println!("{:?}", text.chars().counts());
// {'d': 1, 'o': 2, ' ': 1, 'w': 1, 'e': 1, 'h': 1, 'r': 1, 'l': 3}