Como faço para iterar em um intervalo com uma etapa personalizada?

100

Como posso iterar em um intervalo em Rust com uma etapa diferente de 1? Estou vindo de uma formação C ++, então gostaria de fazer algo como

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

Em Rust, preciso usar a rangefunção e não parece que haja um terceiro argumento disponível para ter uma etapa personalizada. Como posso fazer isso?

Frutose Sintática
fonte

Respostas:

12

Parece-me que até que o .step_bymétodo se torne estável, pode-se facilmente realizar o que você deseja com um Iterator(que é o que Rangerealmente é de qualquer maneira):

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

Se for necessário iterar vários intervalos de diferentes tipos, o código pode ser genérico da seguinte maneira:

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

Vou deixar para você eliminar a verificação de limites superiores para criar uma estrutura aberta se um loop infinito for necessário ...

A vantagem dessa abordagem é que funciona com foradição de açúcar e continuará a funcionar mesmo quando recursos instáveis ​​se tornarem utilizáveis; Além disso, ao contrário da abordagem sem açúcar usando os padrões Range, ele não perde eficiência com várias .next()chamadas. As desvantagens são que são necessárias algumas linhas de código para configurar o iterador, portanto, pode valer a pena apenas para código que tem muitos loops.

GordonBGood
fonte
Ao adicionar outro tipo, Uem sua segunda opção, você pode usar tipos que suportam adição com um tipo diferente e ainda produzir a T. Por exemplo, tempo e duração vêm à mente.
Ryan
@Ryan, parece uma boa ideia e deve funcionar, com a estrutura definida da seguinte forma: struct StepRange <T> (T, T, U) onde para <'a,' b> & 'a T: Adicionar <&' b U, Saída = T>, T: PartialOrd, T: Clone; que deve permitir diferentes tempos de vida para os tipos T e U de entrada.
GordonBGood
3

Você escreveria seu código C ++:

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

... em Rust assim:

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

Acho que a versão Rust também é mais legível.

kmky
fonte
Re: inserir "continue" no loop, só faria isso dentro de uma ramificação condicional, mesmo na estrutura for, eu acho. Se for assim, acho que não há problema em incrementar dentro da ramificação condicional na estrutura while antes de "continuar" -ing, e que funcionaria como pretendido. Ou estou esquecendo algo?
WDS
1
@WDS que é um trabalho contra-intuitivo para fazer com que um recurso básico da linguagem continuefuncione corretamente. Embora possa ser feito, esse design incentiva a ocorrência de erros.
Chai T. Rex
2

Se você estiver avançando por algo predefinido e pequeno como 2, pode desejar usar o iterador para avançar manualmente. por exemplo:

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

Você pode até usar isso para definir uma quantidade arbitrária (embora isso esteja definitivamente ficando mais longo e mais 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
fonte