¿Cómo iterar sobre un rango con un paso personalizado?

100

¿Cómo puedo iterar sobre un rango en Rust con un paso que no sea 1? Vengo de una experiencia en C ++, así que me gustaría hacer algo como

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

En Rust necesito usar la rangefunción, y no parece que haya un tercer argumento disponible para tener un paso personalizado. ¿Cómo puedo lograr esto?

Fructosa sintáctica
fuente

Respuestas:

12

Me parece que hasta que el .step_bymétodo se estabilice, uno puede lograr fácilmente lo que desea con un Iterator(que es lo que Rangerealmente son de todos modos):

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);
    }
}

Si uno necesita iterar múltiples rangos de diferentes tipos, el código puede hacerse genérico de la siguiente manera:

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);
    }
}

Dejaré que usted elimine la verificación de límites superiores para crear una estructura abierta si se requiere un bucle infinito ...

Las ventajas de este enfoque es que funciona con el forazúcar y seguirá funcionando incluso cuando las características inestables se vuelvan utilizables; Además, a diferencia del enfoque sin azúcar que usa los estándares Range, no pierde eficiencia por múltiples .next()llamadas. Las desventajas son que se necesitan algunas líneas de código para configurar el iterador, por lo que solo puede valer la pena para el código que tiene muchos bucles.

GordonBGood
fuente
Al agregar otro tipo, Ua su segunda opción puede usar tipos que admitan la adición con un tipo diferente y aún así producir un T. Por ejemplo, me vienen a la mente el tiempo y la duración.
Ryan
@Ryan, eso parece una buena idea y debería funcionar, con la estructura definida de la siguiente manera: struct StepRange <T> (T, T, U) donde para <'a,' b> & 'a T: Add <&' b U, Salida = T>, T: PartialOrd, T: Clonar; lo que debería permitir diferentes vidas útiles para los tipos de entrada T y U.
GordonBGood
3

Escribirías tu código C ++:

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

... en Rust así:

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

Creo que la versión de Rust también es más legible.

kmky
fuente
Re: insertando "continuar" en el bucle, uno solo haría esto dentro de una rama condicional incluso en la estructura for, creo. Si es así, creo que estaría bien incrementar dentro de la rama condicional en la estructura while antes de "continuar" -ing, y que luego funcionaría como se esperaba. ¿O estoy pasando por alto algo?
WDS
1
@WDS es un trabajo contradictorio para que una característica básica del lenguaje continuefuncione correctamente. Aunque se puede hacer, este diseño fomenta los errores.
Chai T. Rex
2

Si está pasando por algo predefinido y pequeño como 2, es posible que desee utilizar el iterador para avanzar manualmente. p.ej:

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

Incluso podría usar esto para pasar por una cantidad arbitraria (aunque definitivamente esto se está volviendo más largo y más difícil de digerir):

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();
    }
}
Leigh McCulloch
fuente