Iterators

Links:

Итераторы

Итераторы позволяют выполнять действия по очереди над цепочкой данных. Итератор берёт каждый объект цепочки и проверяет, не последний или он. Итераторы в 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}