Wie iteriere ich mit einem benutzerdefinierten Schritt über einen Bereich?

94

Wie kann ich mit einem anderen Schritt als 1 über einen Bereich in Rust iterieren? Ich komme aus einem C ++ - Hintergrund, also würde ich gerne so etwas machen

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

In Rust muss ich die rangeFunktion verwenden, und es scheint nicht, dass ein drittes Argument für einen benutzerdefinierten Schritt verfügbar ist. Wie kann ich das erreichen?

Syntaktische Fruktose
quelle

Antworten:

12

Es scheint mir, dass man, bis die .step_byMethode stabil ist, leicht erreichen kann, was man will mit einem Iterator(was Rangeeigentlich sowieso ist):

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

Wenn mehrere Bereiche unterschiedlichen Typs iteriert werden müssen, kann der Code wie folgt generisch gestaltet werden:

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

Ich überlasse es Ihnen, die Prüfung der oberen Grenzen zu eliminieren, um eine Struktur mit offenem Ende zu erstellen, wenn eine Endlosschleife erforderlich ist ...

Der Vorteil dieses Ansatzes besteht darin, dass er mit forZucker arbeitet und auch dann weiter funktioniert, wenn instabile Funktionen verwendet werden können. Im Gegensatz zum Ansatz ohne Zucker, bei dem die Standards verwendet werden Range, geht die Effizienz bei mehreren .next()Anrufen nicht verloren . Nachteile sind, dass zum Einrichten des Iterators einige Codezeilen erforderlich sind. Dies lohnt sich möglicherweise nur für Code mit vielen Schleifen.

GordonBGood
quelle
Durch Hinzufügen eines anderen Typs Uzu Ihrer zweiten Option können Sie Typen verwenden, die das Hinzufügen mit einem anderen Typ unterstützen und dennoch einen ergeben T. Zum Beispiel kommen Zeit und Dauer in den Sinn.
Ryan
@Ryan, das scheint eine gute Idee zu sein und sollte funktionieren, wobei die Struktur wie folgt definiert ist: struct StepRange <T> (T, T, U) wobei für <'a,' b> & 'a T: Add <&' b U, Ausgabe = T>, T: PartialOrd, T: Klon; Dies sollte unterschiedliche Lebensdauern für die Eingangsarten T und U ermöglichen.
GordonBGood
4

Sie würden Ihren C ++ - Code schreiben:

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

... in Rust so:

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

Ich denke, die Rust-Version ist auch besser lesbar.

kmky
quelle
Betreff: Wenn man "continue" in die Schleife einfügt, würde man dies nur innerhalb eines bedingten Zweigs tun, selbst in der for-Struktur, denke ich. Wenn ja, dann denke ich, wäre es in Ordnung, innerhalb des bedingten Zweigs in der while-Struktur zu inkrementieren, bevor Sie fortfahren, und dass es dann wie beabsichtigt funktionieren würde. Oder übersehe ich etwas?
WDS
1
@WDS, das ist nicht intuitiv zu tun, um ein grundlegendes Merkmal der Sprache continuezu erhalten und richtig zu funktionieren. Obwohl dies möglich ist, fördert dieses Design Fehler.
Chai T. Rex
4

Verwenden Sie die num Kiste mit range_step

cambunctious
quelle
2

Wenn Sie an etwas Vordefiniertem und Kleinem wie 2 vorbeikommen, möchten Sie möglicherweise den Iterator verwenden, um manuell zu treten. z.B:

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

Sie können dies sogar verwenden, um eine beliebige Menge zu erhöhen (obwohl dies definitiv länger und schwerer zu verdauen wird):

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
quelle