Une collection peut fournir plusieurs itérateurs via son interface qui fournissent des éléments dans des ordres différents, tels que l'avant et l'arrière.
Un itérateur est souvent implémenté en fonction de la structure sous-jacente à une implémentation de collection et est souvent étroitement couplé à la collection pour permettre la sémantique opérationnelle de l'itérateur.
Un itérateur a un comportement similaire à celui d'un curseur de base de données .
Les itérateurs remontent au langage de programmation CLU en 1974.
compteur de boucle est parfois également appelé itérateur de boucle. Cependant, un compteur de boucle ne fournit que la fonctionnalité de parcours et non celle d'accès aux éléments.Générateur
yield :from typing import Generatordef fibonacci ( limit : int ) -> Generator [ int , None , None ]: a , b = 0 , 1 for _ in range ( limit ): yield a a , b = b , a + bpour nombre dans fibonacci ( 100 ): # Le générateur construit un itérateur print ( nombre )
itérateur interne
Un itérateur interne est une fonction d'ordre supérieur (prenant souvent des fonctions anonymes ) qui parcourt une collection en appliquant une fonction à chaque élément. Par exemple, la fonction Python `map` mapapplique une fonction définie par l'appelant à chaque élément :
Un itérateur implicite se manifeste souvent dans la syntaxe du langage sous la forme foreach.
En Python, un objet collection peut être itéré directement :
Les langages qui prennent en charge les compréhensions de listes ou des constructions similaires peuvent également utiliser des itérateurs implicites lors de la construction de la liste de résultats, comme en Python :
for_each(). Ces fonctions requièrent toujours un objet itérateur explicite en entrée, mais l'itération suivante ne fournit aucun objet itérateur à l'utilisateur.Flux
Par contraste avec l'indexation
Au lieu d'utiliser un itérateur, de nombreux langages permettent d'utiliser un opérateur d'indexation et un compteur de boucle pour accéder à chaque élément. Bien que l'indexation puisse être utilisée avec les collections, l'utilisation d'itérateurs peut présenter des avantages tels que :
- Les boucles de comptage ne conviennent pas à toutes les structures de données, en particulier aux structures de données sans accès aléatoire ou avec un accès aléatoire lent , comme les listes ou les arbres .
- Les itérateurs peuvent fournir une méthode cohérente pour itérer sur des structures de données de toutes sortes, et rendent ainsi le code plus lisible, réutilisable et moins sensible à un changement dans la structure des données .
- Un itérateur peut imposer des restrictions d'accès supplémentaires, par exemple en veillant à ce que les éléments ne puissent pas être ignorés ou qu'un élément déjà visité ne puisse pas être consulté une seconde fois.
- Un itérateur permet de modifier l'objet collection sans être invalidé. Par exemple, une fois l'itérateur passé au-delà du premier élément, il est possible d'insérer des éléments supplémentaires au début de la collection avec des résultats prévisibles. Avec l'indexation, cela pose problème car les numéros d'index doivent être modifiés.
La possibilité de modifier une collection pendant son parcours est devenue essentielle en programmation orientée objet moderne , où les interrelations entre les objets et les effets des opérations peuvent être complexes. L'utilisation d'un itérateur permet de s'affranchir de ces conséquences. Toutefois, il convient de nuancer cette affirmation, car bien souvent, pour des raisons d'efficacité, l'implémentation de l'itérateur est si étroitement liée à la collection qu'elle empêche toute modification de cette dernière sans s'invalider elle-même.
Pour les collections dont les données peuvent être déplacées en mémoire, la seule façon de ne pas invalider l'itérateur est de maintenir, pour la collection, la trace de tous les itérateurs actifs et de les mettre à jour dynamiquement. Comme le nombre d'itérateurs à un instant donné peut être arbitrairement grand par rapport à la taille de la collection liée, leur mise à jour systématique compromet fortement la garantie de complexité des opérations de la collection.
Une autre façon de limiter le nombre de mises à jour à la taille de la collection consiste à utiliser un mécanisme de gestion des données, c'est-à-dire une collection de pointeurs indirects vers les éléments de la collection à mettre à jour. Les itérateurs pointent alors vers ces pointeurs plutôt que directement vers les éléments . Cependant, cette approche dégrade les performances des itérateurs, car elle nécessite un double accès aux données pour accéder à l'élément. Ceci est généralement à éviter, car de nombreux algorithmes utilisant des itérateurs font appel à l'accès aux données plus fréquemment qu'à la méthode d'avancement. Il est donc primordial d'utiliser des itérateurs offrant un accès aux données très efficace.
En définitive, il s'agit toujours d'un compromis entre sécurité (les itérateurs restent toujours valides) et efficacité. La plupart du temps, le gain en sécurité ne justifie pas la perte d'efficacité qu'il engendre. Utiliser une autre méthode de traitement (par exemple, une liste simplement chaînée au lieu d'un vecteur) serait un meilleur choix (globalement plus efficace) si la stabilité des itérateurs est essentielle.
Classification
Catégories
Les itérateurs peuvent être classés selon leur fonctionnalité. Voici une liste (non exhaustive) des catégories d'itérateurs :
| Catégorie | Langues |
|---|---|
| itérateur bidirectionnel | C++ , Rust |
| Itérateur avant | C++ |
| itérateur d'entrée | C++ |
| itérateur de sortie | C++ |
| itérateur à accès aléatoire | C++ |
| Itérateur trivial | C++ (ancienne STL ) |
Types
Différents langages ou bibliothèques utilisés avec ces langages définissent des types d'itérateurs. Certains d'entre eux sont
| Taper | Langues |
|---|---|
| itérateur de tableau | PHP , R |
| itérateur de mise en cache | PHP |
| itérateur constant | C++ , PHP |
| itérateur de répertoire | PHP, Python |
| itérateur de filtre | PHP, R |
| itérateur limite | PHP |
| itérateur de liste | C# , Java , R |
| itérateur de tableau récursif | PHP |
| itérateur XML | PHP |
Dans différents langages de programmation
.FILET
Dans le framework .NET (C#), les itérateurs sont appelés « énumérateurs » et sont représentés par l' IEnumeratorinterface `Enumerator`. IEnumerator Cette interface fournit une MoveNext()méthode `next` qui passe à l'élément suivant et indique si la fin de la collection a été atteinte ; une Currentpropriété permettant d'obtenir la valeur de l'élément actuellement pointé ; et une Reset()méthode optionnelle permettant de rembobiner l'énumérateur à sa position initiale. L'énumérateur pointe initialement vers une valeur spéciale avant le premier élément ; un appel à `next` MoveNext()est donc nécessaire pour commencer l'itération.
Les énumérateurs sont généralement obtenus en appelant la GetEnumerator()méthode d'un objet implémentant l' IEnumerableinterface. une Currentpropriété, pour obtenir la valeur de l'élément actuellement pointé ; Les classes conteneurs implémentent généralement cette interface. Cependant, l' instruction foreach en C# peut opérer sur n'importe quel objet fournissant une telle méthode, même s'il ne l'implémente pas IEnumerable( typage dynamique ). Les deux interfaces ont été étendues en versions génériques dans .NET 2.0 .
L'exemple suivant illustre une utilisation simple des itérateurs en C# 2.0 :
IEnumerator(ou IEnumerable), mais utilisant l' yield returninstruction " " pour produire une séquence d'éléments au lieu de retourner une instance d'objet, sera transformée par le compilateur en une nouvelle classe implémentant l'interface appropriée.C
Les itérateurs n'existent pas nativement en C , mais peuvent être en quelque sorte émulés avec l'arithmétique des pointeurs .
L'exemple suivant illustre une liste chaînée intavec itérateur.
typedef struct LinkedList { int * valeur ; struct LinkedList * suivant ; } LinkedList ;typedef struct { LinkedList * current ; } LinkedListIterator ;LinkedListIterator list_begin ( LinkedList * head ) { LinkedListIterator it = { head }; return it ; }int * list_next ( LinkedListIterator * it ) { if ( ! it -> current ) { return NULL ; } int * value = & it -> current -> value ; it -> current = it -> current -> next ; return value ; }int main () { // ... for ( LinkedListIterator it = list_begin ( head ); ) { int * val = list_next ( & it ) ; if ( ! val ) { break ; } printf ( "%d " , * val ); } }
C++
Le langage C++ utilise largement les itérateurs dans sa bibliothèque standard et décrit plusieurs catégories d'itérateurs, différant par l'ensemble des opérations qu'ils permettent. On trouve, par ordre croissant de possibilités, les itérateurs directs , les itérateurs bidirectionnels et les itérateurs à accès aléatoire . Tous les types modèles de conteneurs standard fournissent des itérateurs appartenant à l'une de ces catégories. Les itérateurs généralisent les pointeurs vers les éléments d'un tableau (qui peuvent effectivement servir d'itérateurs), et leur syntaxe est conçue pour ressembler à celle de l'arithmétique des pointeurs en C : les opérateurs ` &` et `&` permettent de référencer l'élément pointé par l'itérateur, tandis que les opérateurs d'arithmétique des pointeurs, tels que `&`, permettent de modifier les itérateurs lors du parcours d'un conteneur.*->++
Le parcours à l'aide d'itérateurs fait généralement intervenir un itérateur variable et deux itérateurs fixes servant à délimiter l'intervalle à parcourir. La distance entre les itérateurs limitatifs, exprimée en nombre d'applications de l'opérateur ++nécessaires pour transformer la limite inférieure en la limite supérieure, est égale au nombre d'éléments de l'intervalle ; le nombre de valeurs d'itérateur distinctes impliquées est supérieur d'une unité. Par convention, l'itérateur limitatif inférieur pointe vers le premier élément de l'intervalle, tandis que l'itérateur limitatif supérieur ne pointe vers aucun élément de l'intervalle, mais se situe juste au-delà de sa fin. Pour parcourir un conteneur entier, la begin()méthode fournit la limite inférieure et end()la limite supérieure. Cette dernière ne référence aucun élément du conteneur, mais constitue une valeur d'itérateur valide pouvant être comparée.
L'exemple suivant illustre une utilisation typique d'un itérateur.
Les types d'itérateurs sont distincts des types de conteneurs avec lesquels ils sont utilisés, bien que les deux soient souvent employés conjointement. La catégorie de l'itérateur (et donc les opérations qui lui sont associées) dépend généralement du type de conteneur. Par exemple, les tableaux et les vecteurs offrent des itérateurs à accès aléatoire, tandis que les ensembles (qui utilisent une structure chaînée comme implémentation) ne fournissent que des itérateurs bidirectionnels. Un même type de conteneur peut être associé à plusieurs types d'itérateurs. Par exemple, le std::vector<T>type de conteneur permet un parcours soit à l'aide de pointeurs (bruts) vers ses éléments (de type `int` T*), soit à l'aide de valeurs d'un type spécial std::vector<T>::iterator. Un autre type est fourni pour les « itérateurs inverses », dont les opérations sont définies de telle sorte qu'un algorithme effectuant un parcours classique (direct) effectuera en réalité un parcours en sens inverse lorsqu'il est appelé avec des itérateurs inverses. La plupart des conteneurs fournissent également un const_iteratortype distinct pour lequel les opérations permettant de modifier les valeurs pointées ne sont intentionnellement pas définies.
Le parcours simple d'un objet conteneur ou d'une plage de ses éléments (y compris la modification de ces éléments, sauf si un itérateur const_iteratorest utilisé) peut être effectué uniquement à l'aide d'itérateurs. Cependant, les types conteneurs peuvent également fournir des méthodes, telles que `map` insertou erase`filter`, qui modifient la structure du conteneur lui-même ; ces méthodes appartiennent à la classe du conteneur, mais requièrent en outre une ou plusieurs valeurs d'itérateur pour spécifier l'opération souhaitée. Bien qu'il soit possible d'avoir plusieurs itérateurs pointant simultanément vers le même conteneur, les opérations de modification de structure peuvent invalider certaines valeurs d'itérateur (la norme spécifie pour chaque cas si cela est possible) ; l'utilisation d'un itérateur invalide constitue une erreur qui entraînera un comportement indéfini , et de telles erreurs ne sont pas nécessairement signalées par le système d'exécution.
L'itération implicite est également partiellement prise en charge par C++ grâce à l'utilisation de modèles de fonctions standard, tels que std::for_each(), std::copy() et std::accumulate().
Lorsqu'ils sont utilisés, ils doivent être initialisés avec des itérateurs existants, généralement `map` beginet end`filter`, qui définissent la plage sur laquelle l'itération s'effectue. Cependant, aucun objet itérateur explicite n'est exposé par la suite au cours de l'itération. Cet exemple illustre l'utilisation de `map` for_each.
On peut obtenir le même résultat en utilisant std::copy, en passant une std::ostream_iteratorvaleur comme troisième itérateur :
Depuis C++11 , la syntaxe des fonctions lambda permet de spécifier l'opération à itérer directement dans le code, évitant ainsi de définir une fonction nommée. Voici un exemple d'itération for-each utilisant une fonction lambda :
La syntaxe de style Java pour les itérateurs ( Iterator<T>, vs T::iterator) peut être implémentée en C++ de la manière suivante :
Java
Introduite dans la version 1.2 du JDK Java, " ) ; } }
Pour montrer que cette fonction hasNext()peut être appelée à plusieurs reprises, nous l'utilisons pour insérer des virgules entre les éléments, mais pas après le dernier élément.
Cette approche ne dissocie pas correctement l'opération d'avance de l'accès aux données proprement dit. Si l'élément de données doit être utilisé plusieurs fois pour chaque avance, il est nécessaire de le stocker dans une variable temporaire . Lorsqu'une avance est requise sans accès aux données (c'est-à-dire pour ignorer un élément de données donné), l'accès est néanmoins effectué, même si la valeur renvoyée est ignorée dans ce cas.
Pour les types de collections compatibles, la remove()méthode de l'itérateur supprime l'élément le plus récemment visité du conteneur tout en conservant l'itérateur utilisable. L'ajout ou la suppression d'éléments via les méthodes du conteneur (également depuis le même thread ) rend l'itérateur inutilisable. Toute tentative d'accès à l'élément suivant lève une exception. Une exception est également levée s'il ne reste plus d'éléments ( hasNext()la méthode ayant précédemment renvoyé `false`).
De plus, J2SE 5.0 de Java a introduit l' foreach ) améliorée pour itérer sur les collections et les tableaux. IterableElle définit la Scala , les itérateurs disposent d'un large éventail de méthodes similaires aux collections et peuvent être utilisés directement dans les boucles `for`. En effet, itérateurs et collections héritent d'un trait de base commun scala.collection.TraversableOnce. Cependant, grâce à la richesse des méthodes disponibles dans la bibliothèque de collections de Scala, telles que map`map`, `filter` collect, filteretc., il est souvent inutile de manipuler directement les itérateurs lors de la programmation en Scala.
Les itérateurs et collections Java peuvent être automatiquement convertis en itérateurs et collections Scala, respectivement, en ajoutant simplement une ligne de code.
celldes tableaux de type tableau. Dans le cas de l'itération externe, où il incombe à l'utilisateur de faire progresser le parcours et de demander les éléments suivants, il est possible de définir un ensemble d'éléments dans une structure de stockage de type tableau et de parcourir ces éléments à l'aide de la forboucle `for`. Par exemple :hasNext(), utilisables dans une boucle `for`.next()reset()whilePHP

foreachLa boucle `for` de PHP a été introduite dans la version 4.0 et rendue compatible avec les objets comme valeurs dans la version bêta 4.0. Cependant, la prise en charge des itérateurs a été ajoutée dans PHP 5 grâce à l'introduction de l'interface interne `Iterator` Traversable . Les deux principales interfaces permettant d'itérer sur des objets via une foreachboucle dans les scripts PHP sont ` Iterator` Iteratoret `Générateur` IteratorAggregate. Cette dernière ne requiert pas que la classe implémentant l'itération déclare toutes les méthodes nécessaires ; elle implémente plutôt une méthode d'accèsgetIterator ` get` qui renvoie une instance de ` Iterator` Traversable. La bibliothèque standard PHP (SPL ) fournit plusieurs classes pour manipuler des itérateurs spécifiques. PHP prend également en charge les générateurs depuis la version 5.5.
L'implémentation la plus simple consiste à encapsuler un tableau, ce qui peut être utile pour l'indication de type et la dissimulation d'informations .
espace de noms Wikipedia\Itérateur ;classe finale ArrayIterator étend \Iterator { tableau privé $array ;public function __construct ( array $array ) { $this -> array = $array ; }public function rewind () : void { echo 'rewinding' , PHP_EOL ; reset ( $this- > array ); }public function current () { $value = current ( $this- > array ); echo "current: { $value } " , PHP_EOL ; return $value ; }public function key () { $key = key ( $this- > array ); echo "clé : { $key } " , PHP_EOL ; return $key ; }public function next () { $value = next ( $this- > array ); echo "next: { $value } " , PHP_EOL ; return $value ; }public function valid () : bool { $valid = $this- > current () !== false ; echo 'valide : ' , ( $valid ? 'true' : 'false' ), PHP_EOL ; return $valid ; } }
Toutes les méthodes de la classe d'exemple sont utilisées lors de l'exécution d'une boucle foreach complète ( foreach ($iterator as $key => $current) {}). Les méthodes de l'itérateur sont exécutées dans l'ordre suivant :
$iterator->rewind()garantit que la structure interne est mise en place dès le début.$iterator->valid()Renvoie vrai dans cet exemple.$iterator->current()La valeur renvoyée est stockée dans$value.$iterator->key()La valeur renvoyée est stockée dans$key.$iterator->next()passe à l'élément suivant de la structure interne.$iterator->valid()renvoie faux et la boucle est interrompue.
L'exemple suivant illustre une classe PHP implémentant l' Traversableinterface, qui pourrait être encapsulée dans une autre IteratorIteratorclasse pour traiter les données avant leur retour dans la foreachboucle. L'utilisation de cette interface, combinée à la MYSQLI_USE_RESULTconstante, permet aux scripts PHP de parcourir des ensembles de résultats contenant des milliards de lignes avec une consommation de mémoire minimale. Ces fonctionnalités ne sont pas exclusives à PHP ni à ses implémentations de classes MySQL (par exemple, la PDOStatementclasse implémente Traversableégalement l'interface).
mysqli_report ( MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT ); $mysqli = new \mysqli ( 'host.example.com' , 'username' , 'password' , 'database_name' );// La classe \mysqli_result renvoyée par l'appel de méthode implémente l'interface interne Traversable. foreach ( $mysqli -> query ( 'SELECT `a`, `b`, `c` FROM `table`' , MYSQLI_USE_RESULT ) as $row ) { // Traiter la ligne renvoyée, qui est un tableau associatif. }
Python
Les itérateurs en Python sont une composante fondamentale du langage et, dans de nombreux cas, restent invisibles car ils sont utilisés implicitement dans l' instruction for` foreach` , les compréhensions de listes et les expressions génératrices . Tous les types de collections intégrés standard de Python prennent en charge l'itération, ainsi que de nombreuses classes de la bibliothèque standard. L'exemple suivant illustre une itération implicite typique sur une séquence :
items()on peut parcourir la méthode d’un dictionnaire qui renvoie les paires clé-valeur correspondantes sous forme de tuple :Raku
Les itérateurs sont un élément fondamental du langage Raku , même si les utilisateurs n'ont généralement pas à s'en préoccuper. Leur utilisation est masquée par des API d'itération telles que l' forinstruction `map` map, ` grepfilter`, l'indexation de listes avec `map` .[$idx], etc.
L'exemple suivant illustre une itération implicite typique sur une collection de valeurs :
mes @values = 1 , 2 , 3 ; pour @values -> $value { dire $value } # SORTIE : # 1 # 2 # 3
Les tables de hachage Raku peuvent également être parcourues directement, ce qui produit Pairdes objets clé-valeur. La kvméthode `getKeys` permet de parcourir les clés et les valeurs de la table ; la keysméthode `getKeys` permet de parcourir les clés ; et la valuesméthode `getValues` permet de parcourir les valeurs.
mon %mot-à-nombre = 'un' => 1 , 'deux' => 2 , 'trois' => 3 ; pour %mot-à-nombre -> $pair { dire $pair ; } # SORTIE : # trois => 3 # un => 1 # deux => 2pour %word-to-number . kv -> $key , $value { dire "$key: $value" } # SORTIE : # trois : 3 # un : 1 # deux : 2pour %word-to-number . clés -> $key { dire "$key => " ~ %word-to-number { $key }; } # SORTIE : # trois => 3 # un => 1 # deux => 2
Les itérateurs peuvent toutefois être utilisés et définis explicitement. Pour tout type itérable, plusieurs méthodes permettent de contrôler différents aspects du processus d'itération. Par exemple, la iteratorméthode `next` doit renvoyer un Iteratorobjet, et la pull-oneméthode `next` doit produire et renvoyer la valeur suivante si possible, ou renvoyer la valeur sentinelleIterationEnd si aucune autre valeur ne peut être produite. L'exemple suivant illustre une itération équivalente sur une collection à l'aide d'itérateurs explicites :
Rubis
Ruby implémente les itérateurs de manière assez différente ; toutes les itérations sont effectuées en passant des fonctions de rappel (callbacks) aux méthodes conteneurs. Ainsi, Ruby implémente non seulement l'itération de base, mais aussi plusieurs modèles d'itération comme le mappage de fonctions, les filtres et la réduction. Ruby prend également en charge une syntaxe alternative pour la méthode d'itération de baseeach ; les trois exemples suivants sont équivalents :
Plus précisément, la forboucle appelle la into_iter()méthode d'une valeur, laquelle renvoie un itérateur qui, à son tour, transmet les éléments à la boucle. La forboucle (ou toute méthode consommant l'itérateur) se poursuit jusqu'à ce que la next()méthode renvoie une Nonevaleur (les itérations produisant des éléments renvoient une Some(T)valeur, où Test le type de l'élément).
Toutes les collections fournies par la bibliothèque standard implémentent le IntoIteratortrait (c'est-à-dire qu'elles définissent la into_iter()méthode). Les itérateurs eux-mêmes implémentent ce Iteratortrait, ce qui nécessite la définition de la next()méthode. De plus, tout type implémentant ce trait Iteratorse voit automatiquement fournir une implémentation de IntoIteratorcette méthode qui renvoie l'itérateur lui-même.
Les itérateurs prennent en charge divers adaptateurs ( map(), filter(), skip(), take(), etc.) en tant que méthodes fournies automatiquement par le Iteratortrait.
Les utilisateurs peuvent créer des itérateurs personnalisés en créant un type implémentant le Iteratortrait. Les collections personnalisées peuvent implémenter ce IntoIteratortrait et renvoyer un type d'itérateur associé à leurs éléments, permettant ainsi leur utilisation directe dans forles boucles. Ci-dessous, le Fibonaccitype implémente un itérateur personnalisé non borné :
struct Fibonacci ( u64 , u64 );impl Fibonacci { pub fn new () -> Self { Self ( 0 , 1 ) } }impl Itérateur pour Fibonacci { type Item = u64 ;fn next ( & mut self ) - > Option < Self :: Item > { let next = self.0 ; self.0 = self.1 ; self.1 = self.0 + next ;Certains ( suivant ) } }fn main () { let fib = Fibonacci :: new ( ); for n in fib.skip ( 1 ) .step_by ( 2 ) .take ( 4 ) { println! ( " { n}" ); } // Affiche 1, 2, 5 et 13 }
![]()