Rust Iterator
In Rust sind Iteratoren (Iterator) ein leistungsstarkes und flexibles Werkzeug für den schrittweisen Zugriff auf und die Manipulation von Sammlungen (wie Arrays, Vektoren, verkettete Listen usw.).
Rusts Iteratoren werden lazy ausgewertet, was bedeutet, dass der Iterator selbst nicht sofort Operationen ausführt, sondern nur dann Werte erzeugt, wenn sie benötigt werden.
Iteratoren ermöglichen es, Sequenzen deklarativ zu durchlaufen, etwa Elemente von Sammlungstypen wie Arrays, Slices und verketteten Listen.
Die Kernidee hinter Iteratoren besteht darin, den Datenverarbeitungsprozess von den Daten selbst zu trennen, wodurch der Code klarer, lesbarer und wartbarer wird.
In Rust werden Iteratoren definiert, indem das Iterator-Trait implementiert wird.
Die grundlegendste Trait-Methode ist next, die das jeweils nächste Element im Iterator zurückgibt, bis sie None zurückgibt, um das Ende anzuzeigen.
Beispiel
type Item;
fn next(&mut self) -> Option<Self::Item>;
// Andere standardmäßig implementierte Methoden wie map, filter usw.
}
Iteratoren folgen den folgenden Prinzipien:
Faule Auswertung (Faulheit): Iteratoren in Rust sind lazy, was bedeutet, dass der Iterator selbst nicht sofort Berechnungen oder Operationen ausführt, bis man explizit Daten anfordert. Dadurch sind Iteratoren effizient und können unnötige Berechnungen vermeiden.
Ownership- und Borrowing-Prüfungen: Rust-Iteratoren halten sich strikt an die Ownership- und Borrowing-Regeln, wodurch Datenrennen und Speicherfehler vermieden werden. Die Lebensdauer eines Iterators ist an die zugrunde liegenden Daten gebunden, was einen sicheren Zugriff auf die Daten gewährleistet.
Verkettung: Rust-Iteratoren unterstützen Verkettung, das heißt, mehrere Iterator-Methoden können miteinander verknüpft werden, um kombinierte Operationen auszuführen, was den Code prägnant und gut lesbar macht. Zum Beispiel durch die Verwendung von
.map()、.filter()、.collect()und anderen Methoden können Sie komplexe Datenverarbeitungs-Pipelines erstellen.Effiziente Speicherverwaltung: Iteratoren vermeiden unnötige Speicherzuweisungen, weil die meisten Operationen träge ausgewertet werden und erst bei Verwendung direkt durchlaufen werden. Dies ist besonders wichtig für die Verarbeitung großer Datensammlungen.
Abstraktion und Allgemeinheit: Rusts Iteratoren, durch das
IteratorTrait, implementieren Abstraktion und Allgemeinheit. Jeder Typ, der dasIteratorTrait implementiert, kann als Iterator in verschiedenen Kontexten verwendet werden. Dieses Design verbessert die Wiederverwendbarkeit und Modularität des Codes.
Iteratoren erstellen
Der gebräuchlichste Weg führt über die.iter()、.iter_mut()oder.into_iter()Methoden der Sammlung zum Erstellen von Iteratoren:
.iter(): Gibt einen Iterator über die unveränderlichen Referenzen der Sammlung zurück..iter_mut(): Gibt einen Iterator über die veränderlichen Referenzen der Sammlung zurück..into_iter(): Übernimmt die Eigentümerschaft an der Sammlung und erzeugt einen Iterator über die Werte.
Verwenden Sie die Methode iter(), um einen ausleihenden Iterator zu erstellen:
let vec = vec![1, 2, 3, 4, 5]; let iter = vec.iter();
Verwende die Methode iter_mut(), um einen Iterator mit veränderbarer Ausleihe zu erstellen:
let mut vec = vec![1, 2, 3, 4, 5]; let iter_mut = vec.iter_mut();
Verwende die Methode into_iter(), um einen besitzenden Iterator zu erstellen:
let vec = vec![1, 2, 3, 4, 5]; let into_iter = vec.into_iter();
Beispiel
let mut iter = v.iter();
assert_eq!(iter.next(), Some(&1));
assert_eq!(iter.next(), Some(&2));
assert_eq!(iter.next(), Some(&3));
assert_eq!(iter.next(), None); // Iteration endet
Iterator-Methoden
Rust-Iteratoren bieten einen reichen Satz von Methoden zur Verarbeitung von Elementen in Sammlungen, darunter:
map(): Wendet eine gegebene Transformationsfunktion auf jedes Element an.filter(): Filtert Elemente in der Sammlung basierend auf einer gegebenen Bedingung.fold(): Führt eine kumulative Verarbeitung von Elementen in der Sammlung durch.skip(): Überspringt eine angegebene Anzahl von Elementen.take(): Nimmt eine angegebene Anzahl von Elementen.enumerate(): Stellt einen Index für jedes Element bereit.- ......
Verwende die map()-Methode, um jedes Element umzuwandeln:
let vec = vec![1, 2, 3, 4, 5]; let squared_vec: Vec<i32> = vec.iter().map(|x| x * x).collect();
Verwende die filter()-Methode, um Elemente anhand einer Bedingung zu filtern:
let vec = vec![1, 2, 3, 4, 5]; let filtered_vec: Vec<i32> = vec.into_iter().filter(|&x| x % 2 == 0).collect();
Durchlaufen eines Iterators mit einer for-Schleife
Rust bietet die for-Schleifen-Syntax, um über Elemente in einem Iterator zu iterieren, was eine prägnantere und intuitivere Art des Durchlaufens ist.
Rusts for-Schleife verwendet tatsächlich Iteratoren unter der Haube.
let vec = vec![1, 2, 3, 4, 5];
for &num in vec.iter() {
println!("{}", num);
}In dieser Schleife gibt vec.iter() einen Iterator zurück, die for-Schleife durchläuft diesen Iterator, weist jedes Element der Variablen num zu und führt dann den Code im Schleifenkörper aus.
Konsumierende Adapter
Verwende den Iterator, bis er vollständig konsumiert ist.
Iteratoren haben viele Methoden, die den Iterator konsumieren können. Sie geben ein Endergebnis zurück (wie eine Summe, eine Sammlung usw.), indem sie eine Iteration durchführen, und diese Methoden konsumieren den Iterator selbst.
collect(): Wandelt den Iterator in eine Sammlung um (wie einen Vektor, ein HashSet).sum(): Berechnet die Summe aller Elemente im Iterator.product(): Berechnet das Produkt aller Elemente im Iterator.count(): Gibt die Anzahl der Elemente im Iterator zurück.
Beispiel
let sum: i32 = v.iter().sum();
assert_eq!(sum, 6);
Adapter
Iterator-Adapter ermöglichen es dir, den Inhalt eines Iterators durch Methodenverkettung zu ändern oder zu filtern, ohne ihn sofort zu konsumieren.
map(): Wendet eine Funktion auf jedes Element an und gibt einen neuen Iterator zurück.filter(): Filtert Elemente heraus, die eine Bedingung erfüllen.take(n): Gibt nur die erstennElemente des Iterators.skip(n): Überspringt die erstennElemente und gibt einen Iterator der verbleibenden Elemente zurück.
let v = vec![1, 2, 3, 4, 5]; let doubled: Vec<i32> = v.iter().map(|x| x * 2).collect(); assert_eq!(doubled, vec![2, 4, 6, 8, 10]);
Iterator-Ketten
Du kannst mehrere Iterator-Adapter miteinander verketten, um eine Iterator-Kette zu bilden.
Beispiel
let arr = [1, 2, 3, 4, 5];
let mut iter = arr.into_iter().peekable();
while let Some(val) = iter.next() {
if val % 2 == 0 {
continue;
}
println!("{}", val);
}
Collectors
Verwende die collect-Methode, um die Elemente des Iterators in eine Art Sammlung zu sammeln.
let arr = [1, 2, 3, 4, 5]; let sum: i32 = arr.into_iter().sum();
Faule Auswertung
Wie bereits erwähnt, sind Rust-Iteratoren faul, was bedeutet, dass Methoden wie map(), filter() usw. Operationen nicht sofort ausführen. Erst wenn eine konsumierende Methode wie collect() aufgerufen wird, werden die Daten tatsächlich verarbeitet. Dies macht die Verarbeitung von Iteratoren effizienter, indem unnötige Berechnungen vermieden werden.
Benutzerdefinierte Iteratoren
Du kannst auch das Iterator-Trait für deine eigenen Typen implementieren; dazu musst du nur die next()-Methode definieren.
Implementiere zum Beispiel einen einfachen Iterator von 1 bis 5:
Beispiel
count: usize,
}
impl Counter {
fn new() -> Counter {
Counter { count: 0 }
}
}
impl Iterator for Counter {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
self.count += 1;
if self.count <= 5 {
Some(self.count)
} else {
Keine
}
}
}
let mut counter = Counter::new();
while let Some(num) = counter.next() {
println!("{}", num); // Gibt 1 bis 5 aus
}
Parallele Iteratoren
Wenn Sie Operationen in einer Multithread-Umgebung parallelisieren müssen, bietet das rayon-Crate Unterstützung für parallele Iteratoren. Durch die Verwendung von .par_iter() anstelle von .iter() können Sie Iterator-Operationen in einer Multithread-Umgebung beschleunigen.
Iteratoren und Lebensdauern
Die Lebensdauer eines Iterators ist mit der Lebensdauer der Elemente verbunden, über die er iteriert. Ein Iterator kann Elemente ausleihen oder deren Eigentümerschaft übernehmen. Dies wird durch Lebensdauerparameter in der Implementierung des Iterators gesteuert.
Iteratoren und Closures
Iterator-Adapter werden häufig mit Closures verwendet, die es Ihnen ermöglichen, benutzerdefinierte Logik für Iterator-Operationen bereitzustellen.
Iteratoren und Leistung
Iteratoren sind normalerweise sehr effizient, weil sie es dem Compiler ermöglichen, Optimierungen vorzunehmen. Zum Beispiel kann der Compiler Aufrufe von Iterator-Adaptern inline setzen und die Lazy-Evaluation-Funktion von Iteratoren nutzen.
Beispiel
Das folgende Beispiel zeigt, wie man einen Iterator verwendet, um ein Array zu durchlaufen und die Elemente des Arrays auszugeben.
Beispiel
fn main() {
// Definiere ein Array, das Ganzzahlen enthält
let numbers = vec![1, 2, 3, 4, 5];
// Verwende einen Iterator, um das Array zu durchlaufen und jedes Element auszugeben
println!("Iteriere durch das Array:");
for num in numbers.iter() {
println!("{}", num);
}
// Verwende die map-Methode des Iterators, um jedes Element im Array zu quadrieren und die Ergebnisse in einem neuen Array zu sammeln
let squared_numbers: Vec<i32> = numbers.iter().map(|x| x * x).collect();
// Das quadrierte Array ausgeben
println!("Quadrierte Zahlen: {:?}", squared_numbers);
}
Im obigen Code definieren wir zunächst ein Array, das Ganzzahlen enthältnumbers, dann verwenden wir dieiter()Methode, um einen Iterator für das Array zu erhalten, und verwenden eineforSchleife, um den Iterator zu durchlaufen und jedes Element im Array auszugeben. Dann verwenden wir diemap()Methode, um jedes Element im Array zu quadrieren, und verwenden diecollect()Methode, um die Ergebnisse in einem neuen Array zu sammelnsquared_numbers. Geben Sie schließlich das quadrierte Array aus.
Wenn Sie das Programm ausführen, können Sie sehen, dass es jedes Element des ursprünglichen Arrays sowie das neue Array nach dem Quadrieren ausgibt:
Iterating through the array: 1 2 3 4 5 Squared numbers: [1, 4, 9, 16, 25]
Dieses Beispiel demonstriert die grundlegende Verwendung von Iteratoren in Rust, einschließlich Durchlauf, Transformation und Sammeln von Ergebnissen.
Das folgende Beispiel verwendet die Methode filter(), um ein Array zu filtern, und gibt das gefilterte Ergebnis aus:
Beispiel
fn main() {
// Definiere ein Array, das Ganzzahlen enthält
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Verwenden Sie die filter-Methode des Iterators, um das Array zu filtern und gerade Zahlen auszuwählen
let even_numbers: Vec<i32> = numbers.iter().filter(|&x| x % 2 == 0).cloned().collect();
// Das gefilterte Ergebnis ausgeben
println!("Even numbers: {:?}", even_numbers);
}
Im obigen Code definieren wir zunächst ein Array, das Ganzzahlen enthältnumbers, dann verwenden Sie diefilter()filter-Methode des Iterators, um das Array zu filtern, wobei gerade Zahlen ausgewählt werden. In derfilter()Closure der filter-Methode verwenden wir die Modulo-Operation, um zu bestimmen, ob ein Element gerade ist. Schließlich verwenden wir diecloned()Methode, um den Wert jeder geraden Zahl zu klonen, und verwenden diecollect()Methode, um die Ergebnisse in einem neuen Array zu sammelneven_numbersVec. Schließlich wird das gefilterte Ergebnis ausgegeben.
Wenn Sie dieses Programm ausführen, können Sie sehen, dass es alle geraden Zahlen im Array ausgibt:
Even numbers: [2, 4, 6, 8, 10]
Dieses Beispiel demonstriert die Iterator-filter()Methodenverwendung in Rust und wie man andere Methoden kombiniert, um Filteroperationen auf Arrays durchzuführen.
Rust-Iterator-Methoden
Im Folgenden finden Sie einige häufig verwendete Iterator-Methoden in Rust, zusammen mit ihren kurzen Beschreibungen und Beispielen:
| Methodenname | Beschreibung | Beispiel |
|---|---|---|
next() |
Gibt das nächste Element im Iterator zurück. | let mut iter = (1..5).into_iter(); while let Some(val) = iter.next() { println!("{}", val); } |
size_hint() |
Gibt die untere und obere Grenze der Anzahl der verbleibenden Elemente im Iterator zurück. | let iter = (1..10).into_iter(); println!("{:?}", iter.size_hint()); |
count() |
Zählt die Anzahl der Elemente im Iterator. | let count = (1..10).into_iter().count(); |
nth() |
Gibt das n-te Element im Iterator zurück. | let third = (0..10).into_iter().nth(2); |
last() |
Gibt das letzte Element im Iterator zurück. | let last = (1..5).into_iter().last(); |
all() |
Wenn alle Elemente im Iterator eine bestimmte Bedingung erfüllen, gibttrue。 |
let all_positive = (1..=5).into_iter().all(|x| x > 0); |
any() |
Gibt zurück, wenn mindestens ein Element im Iterator eine bestimmte Bedingung erfüllttrue。 |
let any_negative = (1..5).into_iter().any(|x| x < 0); |
find() |
Gibt das erste Element im Iterator zurück, das eine bestimmte Bedingung erfüllt. | let first_even = (1..10).into_iter().find(|x| x % 2 == 0); |
find_map() |
Wendet eine Funktion auf die Elemente des Iterators an und gibt das erste zurückgegebeneSomeErgebnis. |
let first_letter = "hello".chars().find_map(|c| if c.is_alphabetic() { Some(c) } else { None }); |
map() |
Wendet eine Funktion auf jedes Element im Iterator an. | let squares: Vec<i32> = (1..5).into_iter().map(|x| x * x).collect();
|
filter() |
Behält Elemente im Iterator bei, die eine bestimmte Bedingung erfüllen. | let evens: Vec<i32> = (1..10).into_iter().filter(|x| x % 2 == 0).collect(); |
filter_map() |
Wendet eine Funktion auf die Elemente des Iterators an; wenn die Funktion zurückgibtSome, wird das Ergebnis beibehalten. |
let chars: Vec<char> = "hello".chars().filter_map(|c| if c.is_alphabetic() { Some(c.to_ascii_uppercase()) } else { None }).collect(); |
map_while() |
Wendet eine Funktion auf die Elemente des Iterators an, bis die Funktion zurückgibtNone。 |
let first_three = (1..).into_iter().map_while(|x| if x <= 3 { Some(x) } else { None }); |
take_while() |
Entnimmt Elemente aus dem Iterator, die eine Bedingung erfüllen, bis eines sie nicht erfüllt. | let first_five = (1..10).into_iter().take_while(|x| x <= 5).collect::<Vec<_>>() |
skip_while() |
Überspringt Elemente im Iterator, die eine Bedingung erfüllen, bis eines sie nicht erfüllt. | let odds: Vec<i32> = (1..10).into_iter().skip_while(|x| x % 2 == 0).collect(); |
for_each() |
Führt für jedes Element im Iterator eine Operation aus. | let mut counter = 0; (1..5).into_iter().for_each(|x| counter += x); |
fold() |
Faltet die Elemente des Iterators mithilfe eines Akkumulators. | let sum: i32 = (1..5).into_iter().fold(0, |acc, x| acc + x); |
try_fold() |
Faltet die Elemente des Iterators, wobei bei Auftreten eines Fehlers möglicherweise vorzeitig zurückgekehrt wird. | let result: Result |
scan() |
Führt eine zustandsbehaftete Faltung auf den Elementen des Iterators aus. | let sum: Vec<i32> = (1..5).into_iter().scan(0, |acc, x| { *acc += x; Some(*acc) }).collect(); |
take() |
Entnimmt höchstens n Elemente aus dem Iterator. | let first_five = (1..10).into_iter().take(5).collect::<Vec<_>>() |
skip() |
Überspringt die ersten n Elemente im Iterator. | let after_five = (1..10).into_iter().skip(5).collect::<Vec<_>>() |
zip() |
Paart Elemente aus zwei Iteratoren zu Tupeln. | let zipped = (1..3).zip(&['a', 'b', 'c']).collect::<Vec<_>>() |
cycle() |
Wiederholt die Elemente des Iterators unendlich oft. | let repeated = (1..3).into_iter().cycle().take(7).collect::<Vec<_>>() |
chain() |
Verkettet mehrere Iteratoren. | let combined = (1..3).chain(4..6).collect::<Vec<_>>() |
rev() |
Kehrt die Reihenfolge der Elemente im Iterator um. | let reversed = (1..4).into_iter().rev().collect::<Vec<_>>() |
enumerate() |
Fügt jedem Element im Iterator einen Index hinzu. | let enumerated = (1..4).into_iter().enumerate().collect::<Vec<_>>() |
peeking_take_while() |
Entnimmt Elemente, die eine Bedingung erfüllen, wobei der Zustand des Iterators erhalten bleibt, sodass nachfolgende Elemente später entnommen werden können. | let (first, rest) = (1..10).into_iter().peeking_take_while(|&x| x < 5); |
step_by() |
Gibt Elemente des Iterators mit einer angegebenen Schrittweite zurück. | let even_numbers = (0..10).into_iter().step_by(2).collect::<Vec<_>>() |
fuse() |
Erzeugt einen zusätzlichen Iterator, der auch nach Erschöpfung des Iterators noch aufgerufen werden kannnext()Methode. |
let mut iter = (1..5).into_iter().fuse(); while iter.next().is_some() {} |
inspect() |
Führt beim Entgegennehmen jedes Elements eine Closure aus, ändert das Element jedoch nicht. | let mut counter = 0; (1..5).into_iter().inspect(|x| println!("Inspecting: {}", x)).for_each(|x| println!("Processing: {}", x));
|
same_items() |
Vergleicht, ob zwei Iteratoren dieselbe Elementfolge erzeugen. | let equal = (1..5).into_iter().same_items((1..5).into_iter()); |
Zusammenfassung
Rusts Iteratoren sind ein leistungsstarkes und flexibles Werkzeug, das es ermöglicht, Sequenzen deklarativ zu verarbeiten. Das Design der Iteratoren berücksichtigt Sicherheit, Leistung und Ausdruckskraft und macht sie zu einem der Kernmerkmale der Sprache Rust. Mit Iteratoren können Rust-Programmierer Code schreiben, der sowohl sicher als auch effizient ist.
Andere Erweiterungen