Itérateur Rust
En Rust, les itérateurs (Iterator) sont un outil puissant et flexible pour accéder et manipuler pas à pas des collections (comme les tableaux, vecteurs, listes chaînées, etc.).
Les itérateurs de Rust sont évalués de manière paresseuse, ce qui signifie que l'itérateur lui-même n'effectue pas immédiatement d'opérations, mais ne produit des valeurs que lorsque vous en avez besoin.
Les itérateurs vous permettent de parcourir des séquences de manière déclarative, comme les éléments de types de collections tels que les tableaux, les tranches et les listes chaînées.
L'idée centrale derrière les itérateurs est de séparer le processus de traitement des données des données elles-mêmes, rendant le code plus clair, plus lisible et plus facile à maintenir.
En Rust, les itérateurs sont définis en implémentant le trait Iterator.
La méthode de trait la plus fondamentale est next, qui renvoie l'élément suivant de l'itérateur un par un, jusqu'à ce qu'elle renvoie None pour indiquer la fin.
Exemple
type Item;
fn next(&mut self) -> Option<Self::Item>;
// Autres méthodes implémentées par défaut telles que map, filter, etc.
}
Les itérateurs suivent les principes suivants :
Évaluation paresseuse (Laziness): Les itérateurs en Rust sont paresseux, ce qui signifie que l'itérateur lui-même n'effectue immédiatement aucun calcul ni aucune opération tant que vous ne demandez pas explicitement des données. Cela rend les itérateurs performants en termes d'efficacité et permet d'éviter les calculs inutiles.
Vérifications de la propriété et de l'emprunt: Les itérateurs Rust respectent strictement les règles de propriété et d'emprunt, évitant les courses de données et les erreurs de mémoire. La durée de vie d'un itérateur est liée aux données sous-jacentes, garantissant un accès sûr aux données.
Chaînage: Les itérateurs Rust prennent en charge le chaînage, c'est-à-dire que plusieurs méthodes d'itérateur peuvent être liées ensemble pour effectuer des opérations combinées, ce qui rend le code concis et très lisible. Par exemple, en utilisant
.map()、.filter()、.collect()et d'autres méthodes, vous pouvez créer des pipelines complexes de traitement de données.Gestion efficace de la mémoire: Les itérateurs évitent les allocations de mémoire inutiles car la plupart des opérations sont évaluées paresseusement et parcourent directement lorsqu'elles sont utilisées. Ceci est particulièrement important pour traiter de grandes collections de données.
Abstraction et généralité: les itérateurs de Rust, à travers le
Iteratortrait, mettent en œuvre l'abstraction et la généralité. Tout type qui implémente leIteratortrait peut être utilisé comme itérateur dans différents contextes. Cette conception améliore la réutilisabilité et la modularité du code.
Création d'itérateurs
La façon la plus courante est via les.iter()、.iter_mut()ou.into_iter()méthodes de la collection pour créer des itérateurs :
.iter(): Renvoie un itérateur sur les références immuables de la collection..iter_mut(): Renvoie un itérateur sur les références mutables de la collection..into_iter(): Prend possession de la collection et produit un itérateur sur les valeurs.
Utilisez la méthode iter() pour créer un itérateur d'emprunt :
let vec = vec![1, 2, 3, 4, 5]; let iter = vec.iter();
Utilisez la méthode iter_mut() pour créer un itérateur d'emprunt mutable :
let mut vec = vec![1, 2, 3, 4, 5]; let iter_mut = vec.iter_mut();
Utilisez la méthode into_iter() pour créer un itérateur qui prend possession :
let vec = vec![1, 2, 3, 4, 5]; let into_iter = vec.into_iter();
Exemple
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); // L'itération se termine
Méthodes d'itérateur
Les itérateurs de Rust offrent un riche ensemble de méthodes pour traiter les éléments des collections, dont voici quelques-unes :
map(): Applique une fonction de transformation donnée à chaque élément.filter(): Filtre les éléments de la collection en fonction d'une condition donnée.fold(): Effectue un traitement cumulatif sur les éléments de la collection.skip(): Saute un nombre spécifié d'éléments.take(): Prend un nombre spécifié d'éléments.enumerate(): Fournit un index pour chaque élément.- ......
Utilisez la méthode map() pour transformer chaque élément :
let vec = vec![1, 2, 3, 4, 5]; let squared_vec: Vec<i32> = vec.iter().map(|x| x * x).collect();
Utilisez la méthode filter() pour filtrer les éléments selon une condition :
let vec = vec![1, 2, 3, 4, 5]; let filtered_vec: Vec<i32> = vec.into_iter().filter(|&x| x % 2 == 0).collect();
Parcourir un itérateur avec une boucle for
Rust fournit la syntaxe de boucle for pour parcourir les éléments d'un itérateur, ce qui constitue une façon de parcourir plus concise et plus intuitive.
La boucle for de Rust utilise en réalité des itérateurs sous le capot.
let vec = vec![1, 2, 3, 4, 5];
for &num in vec.iter() {
println!("{}", num);
}Dans cette boucle, vec.iter() renvoie un itérateur, la boucle for parcourt cet itérateur, affecte chaque élément à la variable num, puis exécute le code dans le corps de la boucle.
Adaptateurs consommateurs
Utilisez l'itérateur jusqu'à ce qu'il soit complètement consommé.
Les itérateurs ont de nombreuses méthodes qui peuvent consommer l'itérateur. Elles renvoient un résultat final (comme une somme, une collection, etc.) en effectuant une itération, et ces méthodes consomment l'itérateur lui-même.
collect(): Convertit l'itérateur en une collection (comme un vecteur, un ensemble de hachage).sum(): Calcule la somme de tous les éléments de l'itérateur.product(): Calcule le produit de tous les éléments de l’itérateur.count(): Renvoie le nombre d’éléments de l’itérateur.
Exemple
let sum: i32 = v.iter().sum();
assert_eq!(sum, 6);
适配器
Les adaptateurs d’itérateur vous permettent de modifier ou filtrer le contenu d’un itérateur par chaînage de méthodes, sans le consommer immédiatement.
map(): Applique une fonction à chaque élément et renvoie un nouvel itérateur.filter(): Filtre les éléments qui satisfont une condition.take(n): Renvoie uniquement les premiersnéléments de l’itérateur.skip(n): Saute les premiersnéléments et renvoie un itérateur des éléments restants.
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]);
Chaînes d’itérateurs
Vous pouvez lier plusieurs adaptateurs d'itérateur ensemble pour former une chaîne d'itérateurs.
Exemple
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
Utilisez la méthode collect pour collecter les éléments de l'itérateur dans une sorte de collection.
let arr = [1, 2, 3, 4, 5]; let sum: i32 = arr.into_iter().sum();
Évaluation paresseuse
Comme mentionné précédemment, les itérateurs Rust sont paresseux, ce qui signifie que des méthodes comme map(), filter(), etc., n'effectuent pas immédiatement les opérations. Ce n'est que lorsqu'une méthode consommatrice comme collect() est appelée que les données sont réellement traitées. Cela rend le traitement des itérateurs plus efficace en évitant les calculs inutiles.
Itérateurs personnalisés
Vous pouvez également implémenter le trait Iterator pour vos propres types, il suffit de définir la méthode next().
Par exemple, implémentez un itérateur simple de 1 à 5 :
Exemple
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 {
Aucune
}
}
}
let mut counter = Counter::new();
while let Some(num) = counter.next() {
println!("{}", num); // Affiche 1 à 5
}
Itérateurs parallèles
Si vous devez paralléliser des opérations dans un environnement multithread, la crate rayon fournit la prise en charge des itérateurs parallèles. En utilisant .par_iter() au lieu de .iter(), vous pouvez accélérer les opérations d'itérateur dans un environnement multithread.
Itérateurs et durées de vie
La durée de vie d'un itérateur est associée à la durée de vie des éléments qu'il parcourt. Un itérateur peut emprunter des éléments ou en prendre possession. Cela est contrôlé par des paramètres de durée de vie dans l'implémentation de l'itérateur.
Itérateurs et fermetures
Les adaptateurs d'itérateur sont souvent utilisés avec des fermetures, qui vous permettent de fournir une logique personnalisée pour les opérations d'itérateur.
Itérateurs et performances
Les itérateurs sont généralement très efficaces parce qu'ils permettent au compilateur de faire des optimisations. Par exemple, le compilateur peut intégrer les appels aux adaptateurs d'itérateur et tirer parti de la fonctionnalité d'évaluation paresseuse des itérateurs.
Exemple
L'exemple suivant montre comment utiliser un itérateur pour parcourir un tableau et afficher les éléments du tableau.
Exemple
fn main() {
// Définir un tableau contenant des entiers
let numbers = vec![1, 2, 3, 4, 5];
// Utiliser un itérateur pour parcourir le tableau et afficher chaque élément
println!("Parcours du tableau :");
for num in numbers.iter() {
println!("{}", num);
}
// Utilisez la méthode map de l'itérateur pour mettre au carré chaque élément du tableau et collecter les résultats dans un nouveau tableau
let squared_numbers: Vec<i32> = numbers.iter().map(|x| x * x).collect();
// Afficher le tableau des carrés
println!("Nombres au carré : {:?}", squared_numbers);
}
Dans le code ci-dessus, on définit d'abord un tableau contenant des entiersnumbers, puis on utilise laiter()méthode pour obtenir un itérateur pour le tableau, et on utilise uneforboucle pour parcourir l'itérateur, en affichant chaque élément du tableau. Ensuite, on utilise lamap()méthode de l'itérateur pour mettre au carré chaque élément du tableau, et on utilise lacollect()méthode pour collecter les résultats dans un nouveau tableausquared_numbers. Enfin, affichez le tableau au carré.
Lorsque vous exécutez le programme, vous pouvez voir qu'il affiche chaque élément du tableau d'origine, ainsi que le nouveau tableau après mise au carré :
Iterating through the array: 1 2 3 4 5 Squared numbers: [1, 4, 9, 16, 25]
Cet exemple illustre l'utilisation de base des itérateurs en Rust, y compris le parcours, la transformation et la collecte des résultats.
L'exemple suivant utilise la méthode filter() pour filtrer un tableau et affiche le résultat filtré :
Exemple
fn main() {
// Définir un tableau contenant des entiers
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Utiliser la méthode filter de l'itérateur pour filtrer le tableau, en sélectionnant les nombres pairs
let even_numbers: Vec<i32> = numbers.iter().filter(|&x| x % 2 == 0).cloned().collect();
// Afficher le résultat filtré
println!("Nombres pairs : {:?}", even_numbers);
}
Dans le code ci-dessus, on définit d'abord un tableau contenant des entiersnumbers, puis utilisez lafilter()méthode filter de l'itérateur pour filtrer le tableau, en sélectionnant les nombres pairs. Dans lafilter()closure de la méthode filter, nous utilisons l'opération modulo pour déterminer si un élément est pair. Enfin, nous utilisons lacloned()méthode pour cloner la valeur de chaque nombre pair, et utilisons lacollect()méthode pour collecter les résultats dans un nouveau tableaueven_numbersVec. Enfin, le résultat filtré est affiché.
En exécutant ce programme, vous pouvez voir qu'il affiche tous les nombres pairs du tableau :
Even numbers: [2, 4, 6, 8, 10]
Cet exemple démontre l'utilisation de la méthode de l'itérateurfilter()en Rust, et comment combiner d'autres méthodes pour effectuer des opérations de filtrage sur des tableaux.
Méthodes d'itérateur en Rust
Voici quelques méthodes d'itérateur couramment utilisées en Rust, accompagnées de brèves descriptions et d'exemples :
| Nom de la méthode | Description | Exemple |
|---|---|---|
next() |
Renvoie l'élément suivant dans l'itérateur. | let mut iter = (1..5).into_iter(); while let Some(val) = iter.next() { println!("{}", val); } |
size_hint() |
Renvoie les bornes inférieure et supérieure du nombre d'éléments restants dans l'itérateur. | let iter = (1..10).into_iter(); println!("{:?}", iter.size_hint()); |
count() |
Compte le nombre d'éléments dans l'itérateur. | let count = (1..10).into_iter().count(); |
nth() |
Renvoie le n-ième élément dans l'itérateur. | let third = (0..10).into_iter().nth(2); |
last() |
Renvoie le dernier élément dans l'itérateur. | let last = (1..5).into_iter().last(); |
all() |
Si tous les éléments de l'itérateur satisfont une certaine condition, renvoietrue。 |
let all_positive = (1..=5).into_iter().all(|x| x > 0); |
any() |
Si au moins un élément de l'itérateur satisfait une certaine condition, renvoietrue。 |
let any_negative = (1..5).into_iter().any(|x| x < 0); |
find() |
Renvoie le premier élément de l'itérateur qui satisfait une certaine condition. | let first_even = (1..10).into_iter().find(|x| x % 2 == 0); |
find_map() |
Applique une fonction aux éléments de l'itérateur, renvoyant le premierSomerésultat renvoyé. |
let first_letter = "hello".chars().find_map(|c| if c.is_alphabetic() { Some(c) } else { None }); |
map() |
Applique une fonction à chaque élément de l'itérateur. | let squares: Vec<i32> = (1..5).into_iter().map(|x| x * x).collect();
|
filter() |
Conserve les éléments de l'itérateur qui satisfont une certaine condition. | let evens: Vec<i32> = (1..10).into_iter().filter(|x| x % 2 == 0).collect(); |
filter_map() |
Applique une fonction aux éléments de l'itérateur ; si la fonction renvoieSome, le résultat est conservé. |
let chars: Vec<char> = "hello".chars().filter_map(|c| if c.is_alphabetic() { Some(c.to_ascii_uppercase()) } else { None }).collect(); |
map_while() |
Applique une fonction aux éléments de l'itérateur jusqu'à ce que la fonction renvoieNone。 |
let first_three = (1..).into_iter().map_while(|x| if x <= 3 { Some(x) } else { None }); |
take_while() |
Prend les éléments de l'itérateur qui satisfont une condition, jusqu'à ce qu'un élément ne la satisfasse pas. | let first_five = (1..10).into_iter().take_while(|x| x <= 5).collect::<Vec<_>>() |
skip_while() |
Ignore les éléments de l'itérateur qui satisfont une condition, jusqu'à ce qu'un élément ne la satisfasse pas. | let odds: Vec<i32> = (1..10).into_iter().skip_while(|x| x % 2 == 0).collect(); |
for_each() |
Effectue une opération sur chaque élément de l'itérateur. | let mut counter = 0; (1..5).into_iter().for_each(|x| counter += x); |
fold() |
Replie les éléments de l'itérateur à l'aide d'un accumulateur. | let sum: i32 = (1..5).into_iter().fold(0, |acc, x| acc + x); |
try_fold() |
Replie les éléments de l'itérateur, avec possibilité de retour anticipé en cas d'erreur. | let result: Result |
scan() |
Effectue un repli avec état sur les éléments de l'itérateur. | let sum: Vec<i32> = (1..5).into_iter().scan(0, |acc, x| { *acc += x; Some(*acc) }).collect(); |
take() |
Prend au plus n éléments de l'itérateur. | let first_five = (1..10).into_iter().take(5).collect::<Vec<_>>() |
skip() |
Ignore les n premiers éléments de l'itérateur. | let after_five = (1..10).into_iter().skip(5).collect::<Vec<_>>() |
zip() |
Apparie les éléments de deux itérateurs en tuples. | let zipped = (1..3).zip(&['a', 'b', 'c']).collect::<Vec<_>>() |
cycle() |
Répète les éléments de l'itérateur à l'infini. | let repeated = (1..3).into_iter().cycle().take(7).collect::<Vec<_>>() |
chain() |
Concatène plusieurs itérateurs. | let combined = (1..3).chain(4..6).collect::<Vec<_>>() |
rev() |
Inverse l'ordre des éléments dans l'itérateur. | let reversed = (1..4).into_iter().rev().collect::<Vec<_>>() |
enumerate() |
Ajoute un index à chaque élément de l'itérateur. | let enumerated = (1..4).into_iter().enumerate().collect::<Vec<_>>() |
peeking_take_while() |
Prend les éléments qui satisfont une condition tout en préservant l'état de l'itérateur, ce qui permet de prendre des éléments suivants plus tard. | let (first, rest) = (1..10).into_iter().peeking_take_while(|&x| x < 5); |
step_by() |
Renvoie les éléments de l'itérateur selon un pas spécifié. | let even_numbers = (0..10).into_iter().step_by(2).collect::<Vec<_>>() |
fuse() |
Crée un itérateur supplémentaire qui peut encore être appelé après que l'itérateur est épuisénext()méthode. |
let mut iter = (1..5).into_iter().fuse(); while iter.next().is_some() {} |
inspect() |
Exécute une fermeture lors de la prise de chaque élément, mais ne modifie pas l'élément. | let mut counter = 0; (1..5).into_iter().inspect(|x| println!("Inspecting: {}", x)).for_each(|x| println!("Processing: {}", x));
|
same_items() |
Compare si deux itérateurs produisent la même séquence d'éléments. | let equal = (1..5).into_iter().same_items((1..5).into_iter()); |
Résumé
Les itérateurs de Rust sont un outil puissant et flexible qui permet de traiter des séquences de manière déclarative. La conception des itérateurs prend en compte la sûreté, la performance et l'expressivité, ce qui en fait l'une des fonctionnalités centrales du langage Rust. Avec les itérateurs, les programmeurs Rust peuvent écrire du code à la fois sûr et efficace.
Autres extensions