Как перебрать диапазон с помощью настраиваемого шага?

100

Как я могу перебрать диапазон в Rust с шагом, отличным от 1? Я работаю с C ++, поэтому я хотел бы сделать что-нибудь вроде

for(auto i = 0; i <= n; i+=2) {
    //...
}

В Rust мне нужно использовать эту rangeфункцию, и не похоже, что есть третий аргумент для использования настраиваемого шага. Как я могу этого добиться?

Синтаксическая фруктоза
источник

Ответы:

137

range_step_inclusiveи range_stepдавно ушли.

Начиная с Rust 1.28, Iterator::step_byстабильно:

fn main() {
    for x in (1..10).step_by(2) {
        println!("{}", x);
    }
}
Таблетка
источник
Обратите внимание, что этот метод не поддерживает 64-битный шаг на машинах с 32-битным размером.
user202729
12

Мне кажется, что до тех пор, пока .step_byметод не станет стабильным, можно легко выполнить то, что вы хотите, с помощью Iterator(что Rangeв любом случае действительно есть):

struct SimpleStepRange(isize, isize, isize);  // start, end, and step

impl Iterator for SimpleStepRange {
    type Item = isize;

    #[inline]
    fn next(&mut self) -> Option<isize> {
        if self.0 < self.1 {
            let v = self.0;
            self.0 = v + self.2;
            Some(v)
        } else {
            None
        }
    }
}

fn main() {
    for i in SimpleStepRange(0, 10, 2) {
        println!("{}", i);
    }
}

Если нужно перебрать несколько диапазонов разных типов, код можно сделать универсальным следующим образом:

use std::ops::Add;

struct StepRange<T>(T, T, T)
    where for<'a> &'a T: Add<&'a T, Output = T>,
          T: PartialOrd,
          T: Clone;

impl<T> Iterator for StepRange<T>
    where for<'a> &'a T: Add<&'a T, Output = T>,
          T: PartialOrd,
          T: Clone
{
    type Item = T;

    #[inline]
    fn next(&mut self) -> Option<T> {
        if self.0 < self.1 {
            let v = self.0.clone();
            self.0 = &v + &self.2;
            Some(v)
        } else {
            None
        }
    }
}

fn main() {
    for i in StepRange(0u64, 10u64, 2u64) {
        println!("{}", i);
    }
}

Я оставлю вам исключить проверку верхних границ, чтобы создать структуру с открытым концом, если требуется бесконечный цикл ...

Преимущества этого подхода в том, что он работает с forшугарингом и будет продолжать работать, даже когда нестабильные функции станут доступны для использования; Кроме того, в отличие от подхода без сахара, использующего стандартные Ranges, он не теряет эффективности из-за нескольких .next()вызовов. Недостатки заключаются в том, что для настройки итератора требуется несколько строк кода, поэтому оно может иметь смысл только для кода с большим количеством циклов.

ГордонБХорошо
источник
Добавив еще один тип Uко второму варианту, вы можете использовать типы, которые поддерживают добавление с другим типом и по-прежнему дают T. Например, на ум приходят время и продолжительность.
Райан
@Ryan, это кажется хорошей идеей и должно работать со структурой, определенной следующим образом: struct StepRange <T> (T, T, U), где для <'a,' b> & 'a T: Add <&' b U, Выход = T>, T: PartialOrd, T: Клонировать; что должно обеспечивать разный срок службы для входных типов T и U.
GordonBGood
3

Вы бы написали свой код на C ++:

for (auto i = 0; i <= n; i += 2) {
    //...
}

... в Rust вот так:

let mut i = 0;
while i <= n {
    // ...
    i += 2;
}

Я думаю, что версия на Rust тоже более читабельна.

kmky
источник
Re: вставив "continue" в цикл, я думаю, это можно сделать только внутри условной ветки даже в структуре for. Если это так, то я думаю, что было бы нормально увеличивать внутри условной ветки в структуре while перед «continue» -ing, и тогда это будет работать так, как задумано. Или я что-то не замечаю?
WDS
1
@WDS - это нелогичная работа, чтобы получить базовую функцию языка continue, чтобы она работала правильно. Хотя это возможно, такая конструкция поощряет ошибки.
Чай Т. Рекс
2

Если вы переходите по заранее заданному параметру, например, маленькому, например 2, вы можете использовать итератор для ручного перехода. например:

let mut iter = 1..10;
loop {
    match iter.next() {
        Some(x) => {
            println!("{}", x);
        },
        None => break,
    }
    iter.next();
}

Вы даже можете использовать это, чтобы пошагово увеличивать произвольную величину (хотя это определенно становится дольше и труднее переваривать):

let mut iter = 1..10;
let step = 4;
loop {
    match iter.next() {
        Some(x) => {
            println!("{}", x);
        },
        None => break,
    }
    for _ in 0..step-1 {
        iter.next();
    }
}
Ли МакКаллох
источник