EMZETT.
Login

Kurz: Ein Iterator liefert nacheinander Elemente (next() gibt Some(x) oder am Ende None). Iterator-Ketten sind lazy: Es passiert nichts, bis ein Verbraucher (collect, sum, for) sie abruft.

Teil des Kurses Rust

Kapitel 10 von 13 im Kurs Rust. Mit Fortschritt, Quiz und Zertifikat auf der Lernseite.

Das Iterator-Konzept

Ein Iterator liefert nacheinander Elemente (next() gibt Some(x) oder am Ende None). Iterator-Ketten sind lazy: Es passiert nichts, bis ein Verbraucher (collect, sum, for) sie abruft. Dabei sind sie so schnell wie handgeschriebene Schleifen.

fn main() {
    let v = vec![1, 2, 3];
    let mut it = v.iter();
    println!("{:?} {:?} {:?} {:?}", it.next(), it.next(), it.next(), it.next());
 
    let quadrate: Vec<i32> = v.iter().map(|x| x * x).collect();
    let gerade: Vec<&i32> = v.iter().filter(|x| **x % 2 == 0).collect();
    let summe: i32 = v.iter().sum();
    println!("{quadrate:?} {gerade:?} {summe}");
}

Ausgabe:

Some(1) Some(2) Some(3) None
[1, 4, 9] [2] 6

Drei Arten, über eine Collection zu iterieren: iter() (leiht &T), iter_mut() (leiht &mut T), into_iter() (verbraucht, liefert T).

Adapter

fn main() {
    let zahlen = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
 
    let r: Vec<i32> = zahlen.iter().copied().filter(|x| x % 2 == 0).map(|x| x * 10).collect();
    println!("{r:?}");
    println!("{:?}", zahlen.iter().take(3).collect::<Vec<_>>());
    println!("{:?}", zahlen.iter().skip(7).collect::<Vec<_>>());
    println!("{:?}", zahlen.iter().step_by(4).collect::<Vec<_>>());
    println!("{:?}", zahlen.iter().take_while(|&&x| x < 4).collect::<Vec<_>>());
    println!("{:?}", zahlen.iter().skip_while(|&&x| x < 8).collect::<Vec<_>>());
    println!("{:?}", zahlen.iter().rev().take(2).collect::<Vec<_>>());
 
    for (i, x) in ["a", "b", "c"].iter().enumerate() {
        print!("{i}:{x} ");
    }
    println!();
    let namen = ["Mia", "Tom"];
    let alter = [17, 19];
    let paare: Vec<(&str, i32)> = namen.iter().copied().zip(alter.iter().copied()).collect();
    println!("{paare:?}");
 
    let verschachtelt = vec![vec![1, 2], vec![3], vec![]];
    println!("{:?}", verschachtelt.into_iter().flatten().collect::<Vec<_>>());
    println!("{:?}", ["ab", "cd"].iter().flat_map(|s| s.chars()).collect::<String>());
    println!("{:?}", [1, 2, 3].iter().chain([4, 5].iter()).count());
}

Ausgabe:

[20, 40, 60, 80, 100]
[1, 2, 3]
[8, 9, 10]
[1, 5, 9]
[1, 2, 3]
[8, 9, 10]
[10, 9]
0:a 1:b 2:c
[("Mia", 17), ("Tom", 19)]
[1, 2, 3]
"abcd"
5

Verbraucher

fn main() {
    let z = [3, 8, 1, 9, 4];
    println!("{} {}", z.iter().sum::<i32>(), z.iter().product::<i32>());
    println!("{:?} {:?}", z.iter().max(), z.iter().min());
    println!("{:?}", z.iter().max_by_key(|&&x| (x as i32 - 5).abs()));
    println!("{} {}", z.iter().any(|&x| x > 8), z.iter().all(|&x| x > 1));
    println!("{:?} {:?}", z.iter().find(|&&x| x > 5), z.iter().position(|&x| x == 1));
    println!("{}", z.iter().fold(0, |acc, x| acc * 10 + x));
    let (klein, gross): (Vec<i32>, Vec<i32>) = z.iter().partition(|&&x| x < 5);
    println!("{klein:?} {gross:?}");
 
    let texte = ["4", "x", "7"];
    let zahlen: Vec<i32> = texte.iter().filter_map(|s| s.parse().ok()).collect();
    println!("{zahlen:?}");
    let ergebnis: Result<Vec<i32>, _> = texte.iter().map(|s| s.parse::<i32>()).collect();
    println!("{}", ergebnis.is_err());
    let alle: Result<Vec<i32>, _> = ["1", "2"].iter().map(|s| s.parse::<i32>()).collect();
    println!("{alle:?}");
}

Ausgabe:

25 864
Some(9) Some(1)
Some(9)
true false
Some(8) Some(2)
38194
[3, 1, 4] [8, 9]
[4, 7]
true
Ok([1, 2])

collect kann in viele Typen sammeln: Vec, String, HashMap, HashSet und sogar Result<Vec<_>, _> (bricht beim ersten Fehler ab).

Eigene Iteratoren

Implementiere das Trait Iterator und du bekommst alle Adapter geschenkt:

struct Fibonacci { a: u64, b: u64 }
 
impl Iterator for Fibonacci {
    type Item = u64;
    fn next(&mut self) -> Option<u64> {
        let r = self.a;
        self.a = self.b;
        self.b += r;
        Some(r)
    }
}
 
fn main() {
    let f = Fibonacci { a: 0, b: 1 };
    let v: Vec<u64> = f.take(10).collect();
    println!("{v:?}");
    let s: u64 = Fibonacci { a: 0, b: 1 }.take_while(|&x| x < 100).filter(|x| x % 2 == 0).sum();
    println!("{s}");
    println!("{:?}", (1..=5).map(|x| x * x).rev().collect::<Vec<_>>());
    println!("{:?}", std::iter::repeat("ab").take(3).collect::<String>());
    println!("{:?}", std::iter::successors(Some(1), |&x| if x < 100 { Some(x * 3) } else { None }).collect::<Vec<_>>());
}

Ausgabe:

[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
44
[25, 16, 9, 4, 1]
"ababab"
[1, 3, 9, 27, 81, 243]

Closures als Parameter und Rückgabe

fn anwenden<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 { f(x) }
fn macher(faktor: i32) -> impl Fn(i32) -> i32 { move |x| x * faktor }
fn boxed(op: &str) -> Box<dyn Fn(i32, i32) -> i32> {
    match op {
        "+" => Box::new(|a, b| a + b),
        _ => Box::new(|a, b| a * b),
    }
}
 
fn main() {
    println!("{}", anwenden(|x| x + 1, 5));
    let verdopple = macher(2);
    println!("{} {}", verdopple(21), boxed("+")(3, 4));
    let mut woerter = vec!["kiwi", "apfel", "fig"];
    woerter.sort_by_key(|w| w.len());
    println!("{woerter:?}");
    let mut zaehler = 0;
    let mut inc = || { zaehler += 1; zaehler };
    inc(); inc();
    println!("{}", zaehler);
}

Ausgabe:

6
42 7
["fig", "kiwi", "apfel"]
2

Merke

  • Iteratoren sind lazy; erst collect, sum, for … führen sie aus
  • iter() leiht, iter_mut() leiht veränderbar, into_iter() verbraucht
  • Adapter: map, filter, take, skip, zip, enumerate, flatten, rev
  • Verbraucher: sum, fold, any, all, find, position, collect, partition
  • Eigene Iteratoren: Iterator mit next() implementieren

Übungsaufgabe

Bestimme mit einer Iterator-Kette die Summe der Quadrate aller ungeraden Zahlen von 1 bis 20.

Quiz zur Selbstkontrolle

Weiter im Kurs

Zurück: Traits und Generics

Weiter: Smart Pointer, Lifetimes und Threads

Alle Kapitel: Rust im Überblick