Avatar ·

Как итерироваться по диапазону с нестандартным шагом в Rust?

Привет, коллеги! Я начал изучать Rust недавно, и столкнулся с задачей, которая кажется простой, но я никак не могу найти элегантное решение. Мне нужно пройтись по числам от 0 до 100, но не подряд, а с шагом 5. То есть: 0, 5, 10, 15, ..., 100. В Python я бы написал что-то вроде `for i in range(0, 101, 5)`, и всё работает. В Rust я знаю про `for i in 0..=100`, но это даёт все числа подряд. Я попробовал использовать `.step_by()`, но компилятор ругается, что такого метода нет у диапазонов. Потом я нагуглил какой-то трейт `Step`, но он то ли нестабильный, то ли ещё что-то. Вот что я пытался сделать: ```rust fn main() { for i in 0..=100.step_by(5) { println!("{}", i); } } ``` Компилятор выдаёт ошибку: `no method named 'step_by' found for struct 'RangeInclusive'`. Я понимаю, что `RangeInclusive` — это не коллекция, а итератор, но как тогда сделать шаг? Может, нужно как-то преобразовывать диапазон? Или писать свою функцию? Подскажите, пожалуйста, как правильно решить эту задачу в Rust. Заранее спасибо! <<>>

Использование метода step_by() из трейта Iterator

Проблема в вашем коде — приоритет операций. Вы пишете 0..=100.step_by(5), что интерпретируется как 0..=(100.step_by(5)). Rust пытается вызвать step_by на числе 100, а не на диапазоне. Нужно взять весь диапазон в скобки:

fn main() {
    for i in (0..=100).step_by(5) {
        println!("{}", i);
    }
}

Теперь всё работает! step_by(5) — это метод трейта Iterator, который доступен для любого итератора, в том числе для диапазонов Range и RangeInclusive. Он создаёт новый итератор, который пропускает каждые n-1 элементов.

Как это работает под капотом

Метод step_by() возвращает структуру StepBy, которая реализует трейт Iterator. Она хранит исходный итератор и счётчик шагов. При каждом вызове next() она пропускает нужное количество элементов и возвращает следующий.

Важные замечания

  • Направление итерации: step_by() работает только с итераторами, которые движутся вперёд. Для обратного хода используйте rev() перед step_by().
  • Шаг должен быть положительным: step_by(0) вызовет панику. Минимальное значение — 1.
  • Работа с включительными диапазонами: (0..=100).step_by(5) выдаст 0, 5, ..., 100. Если шаг не кратен границе, последний элемент может не войти.
  • Производительность: step_by работает за O(1) по памяти и O(n/m) по времени, где n — размер диапазона, m — шаг.

Примеры для разных сценариев

// Шаг 3 от 1 до 10 (не включая 10)
for i in (1..10).step_by(3) {
    println!("{}", i); // 1, 4, 7
}

// Обратный порядок с шагом 2
for i in (0..=10).rev().step_by(2) {
    println!("{}", i); // 10, 8, 6, 4, 2, 0
}

// Работа с массивами через индексы
let arr = [10, 20, 30, 40, 50];
for i in (0..arr.len()).step_by(2) {
    println!("arr[{}] = {}", i, arr[i]); // arr[0]=10, arr[2]=30, arr[4]=50
}

Альтернативный подход: фильтрация

Если вам нужна более сложная логика шага, можно использовать filter():

for i in (0..=100).filter(|x| x % 5 == 0) {
    println!("{}", i);
}

Но для простых арифметических шагов step_by() эффективнее.

Итог

Главное — не забывайте скобки вокруг диапазона. В Rust step_by() — это стандартный метод итератора, он стабилен и работает начиная с Rust 1.28. Используйте его для любых последовательностей с равномерным шагом.

<<>>

Создание собственного итератора с шагом

Если по какой-то причине step_by() не подходит (например, нужен нелинейный шаг или работа с очень старым Rust), можно написать свой итератор вручную. Это также полезно для понимания того, как работают итераторы в Rust.

struct StepRange {
    current: i32,
    end: i32,
    step: i32,
}

impl Iterator for StepRange {
    type Item = i32;

    fn next(&mut self) -> Option<Self::Item> {
        if self.current > self.end {
            None
        } else {
            let result = self.current;
            self.current += self.step;
            Some(result)
        }
    }
}

fn main() {
    let range = StepRange { current: 0, end: 100, step: 5 };
    for i in range {
        println!("{}", i);
    }
}

Этот подход даёт полный контроль над логикой итерации. Вы можете добавить поддержку разных типов через дженерики, проверку границ и т.д. Однако для 95% случаев встроенный step_by() предпочтительнее — он короче, стандартизирован и оптимизирован.

Ещё один вариант — использовать крейт itertools, который предоставляет множество полезных методов для итераторов, включая step(). Но для такой простой задачи подключать внешнюю зависимость обычно излишне.