Iterator - Iterator

În programarea computerizată , un iterator este un obiect care permite unui programator să traverseze un container , în special liste . Diferite tipuri de iteratori sunt adesea furnizate prin intermediul interfeței unui container . Deși interfața și semantica unui iterator dat sunt fixe, iteratorii sunt adesea implementați în ceea ce privește structurile care stau la baza implementării unui container și sunt adesea strâns cuplați la container pentru a permite semantica operațională a iteratorului. Un iterator efectuează traversări și oferă, de asemenea, acces la elementele de date dintr-un container, dar nu efectuează el însuși iterația (adică nu fără o anumită libertate semnificativă luată cu acel concept sau cu utilizarea banală a terminologiei). Un iterator este similar comportamentului cu un cursor de bază de date . Iteratorii datează limbajului de programare CLU în 1974.

Descriere

Iteratori interni

Iteratorii interni sunt funcții de ordin superior (adesea luând funcții anonime ), cum ar fi harta , reducerea etc., implementând traversarea pe un container, aplicând funcția dată fiecărui element la rândul său.

Iteratori externi și modelul iterator

Un iterator extern poate fi considerat ca un tip de pointer care are două operații principale: referirea la un anumit element din colecția de obiecte (numit acces la element ) și modificarea în sine, astfel încât să indice spre următorul element (numit traversare a elementului ). De asemenea, trebuie să existe o modalitate de a crea un iterator, astfel încât acesta să indice un prim element, precum și o modalitate de a determina când iteratorul a epuizat toate elementele din container. În funcție de limbă și de utilizarea intenționată, iteratorii pot oferi, de asemenea, operații suplimentare sau pot prezenta comportamente diferite.

Scopul principal al unui iterator este de a permite unui utilizator să proceseze fiecare element al unui container, izolându-l în același timp de structura internă a containerului. Acest lucru permite containerului să stocheze elementele în orice mod dorește, permițând în același timp utilizatorului să le trateze ca și cum ar fi o simplă secvență sau listă. O clasă de iteratori este de obicei proiectată în strânsă coordonare cu clasa de containere corespunzătoare. De obicei, containerul oferă metodele de creare a iteratorilor.

Un contor de bucle este uneori denumit și iterator de buclă. Un contor de buclă , cu toate acestea, oferă numai funcționalitatea de traversare și nu funcționalitatea de acces la elemente.

Generatoare

O modalitate de implementare a iteratorilor este utilizarea unei forme restrânse de coroutină , cunoscută sub numele de generator . Spre deosebire de un subrutin , un generator de corutină poate da valori apelantului său de mai multe ori, în loc să se întoarcă o singură dată. Majoritatea iteratorilor sunt exprimabili în mod natural ca generatori, dar, deoarece generatorii își păstrează starea locală între invocații, sunt deosebit de potrivite pentru iteratori complicați, de stare, cum ar fi traversatorii de copaci . Există diferențe și distincții subtile în utilizarea termenilor „generator” și „iterator”, care variază între autori și limbi. În Python , un generator este un constructor iterator : o funcție care returnează un iterator. Urmează un exemplu de generator Python care returnează un iterator pentru numerele Fibonacci folosind yielddeclarația Python :

def fibonacci(limit):
    a, b = 0, 1
    for _ in range(limit):
        yield a
        a, b = b, a + b

for number in fibonacci(100): # The generator constructs an iterator
    print(number)

Iteratori implicați

Unele limbaje orientate obiect, cum ar fi C # , C ++ (versiuni ulterioare), Delphi (versiuni ulterioare), Go , Java (versiuni ulterioare), Lua , Perl , Python , Ruby oferă un mod intrinsec de iterare prin elementele unui obiect container fără introducerea unui obiect iterator explicit. Un obiect iterator real poate exista în realitate, dar dacă există, nu este expus în codul sursă al limbajului.

Iteratorii implicați se manifestă adesea printr-o instrucțiune " foreach " (sau echivalent), cum ar fi în următorul exemplu Python:

for value in iterable:
    print(value)

În Python, un iterabil este un obiect care poate fi convertit într-un iterator, care este apoi iterat în timpul buclei for; acest lucru se face implicit.

Sau alteori pot fi create chiar de obiectul de colecție, ca în acest exemplu Ruby:

iterable.each do |value|
  puts value
end

Acest stil de iterație se numește uneori „iterație internă”, deoarece codul său se execută pe deplin în contextul obiectului iterabil (care controlează toate aspectele iterației), iar programatorul oferă doar operația de executat la fiecare pas (folosind o funcție anonimă ).

Limbile care acceptă înțelegerile listei sau construcții similare pot utiliza, de asemenea, iteratori impliciți în timpul construirii listei de rezultate, ca în Python:

names = [person.name for person in roster if person.male]

Uneori natura ascunsă implicită este doar parțială. C ++ limbaj are cateva template - uri pentru funcția de repetare implicită, cum ar fi for_each(). Aceste funcții necesită încă obiecte iteratoare explicite ca intrare inițială, dar iterația ulterioară nu expune utilizatorului un obiect iterator.

Fluxuri

Iteratorii sunt o abstracție utilă a fluxurilor de intrare - oferă un obiect iterabil (dar nu neapărat indexabil) potențial infinit. Mai multe limbi, cum ar fi Perl și Python, implementează fluxuri ca iteratoare. În Python, iteratorii sunt obiecte care reprezintă fluxuri de date. Implementările alternative ale fluxului includ limbaje bazate pe date , cum ar fi AWK și sed .

În contrast cu indexarea

În limbajele de procedură este obișnuit să folosiți operatorul de indică și un contor de bucle pentru a parcurge toate elementele dintr-o secvență, cum ar fi o matrice. Deși indexarea poate fi utilizată și cu unele containere orientate obiect, utilizarea iteratorilor poate avea unele avantaje:

  • Buclele de numărare nu sunt potrivite pentru toate structurile de date, în special pentru structurile de date fără acces aleator sau lent , cum ar fi listele sau arborii .
  • Iteratorii pot oferi o modalitate consecventă de a itera structuri de date de tot felul și, prin urmare, pot face codul mai lizibil, reutilizabil și mai puțin sensibil la o modificare a structurii datelor.
  • Un iterator poate impune restricții suplimentare la acces, cum ar fi asigurarea faptului că elementele nu pot fi omise sau că un element vizitat anterior nu poate fi accesat a doua oară.
  • Un iterator poate permite modificarea obiectului container fără a invalida iteratorul. De exemplu, odată ce un iterator a avansat dincolo de primul element, poate fi posibil să introduceți elemente suplimentare în începutul containerului cu rezultate previzibile. Cu indexarea, acest lucru este problematic, deoarece numerele de index trebuie să se schimbe.

Abilitatea unui container de a fi modificată în timp ce itera prin elementele sale a devenit necesară în programarea modernă orientată pe obiecte , în care relațiile dintre obiecte și efectele operațiilor ar putea să nu fie evidente. Prin utilizarea unui iterator, unul este izolat de acest tip de consecințe. Această afirmație trebuie totuși luată cu un bob de sare, deoarece de cele mai multe ori, din motive de eficiență, implementarea iteratorului este atât de strâns legată de container încât împiedică modificarea containerului subiacent fără a se invalida.

Pentru containerele care se pot deplasa în memoria datelor, singura modalitate de a nu invalida iteratorul este, pentru container, să țină cumva evidența tuturor iteratorilor în viață în prezent și să le actualizeze din mers. Deoarece numărul de iteratori la un moment dat poate fi în mod arbitrar mare în comparație cu dimensiunea containerului legat, actualizarea acestora va afecta în mod drastic garanția complexității operațiunilor containerului.

O modalitate alternativă de a menține numărul de actualizări legat relativ de dimensiunea containerului ar fi utilizarea unui fel de mecanism de manipulare, adică o colecție de indicatori indirecți către elementele containerului care trebuie actualizate împreună cu containerul și lăsați iteratorii să aceste mânere în loc de direct către elementele de date. Dar această abordare va avea un impact negativ asupra performanței iteratorului, deoarece trebuie să efectueze un indicator dublu următor pentru a accesa elementul de date real. Acest lucru nu este de obicei de dorit, deoarece mulți algoritmi care utilizează iteratorii invocă operația de acces la date iteratori mai des decât metoda avansată. Prin urmare, este deosebit de important să aveți iteratori cu acces la date foarte eficient.

Una peste alta, acesta este întotdeauna un compromis între securitate (iteratorii rămân mereu valabili) și eficiență. De cele mai multe ori, securitatea adăugată nu merită prețul de eficiență pe care trebuie să îl plătiți pentru aceasta. Utilizarea unui container alternativ (de exemplu, o listă legată individual în locul unui vector) ar fi o alegere mai bună (global mai eficientă) dacă este necesară stabilitatea iteratorilor.

Clasificarea iteratorilor

Categorii de iteratoare

Iteratorii pot fi clasificați în funcție de funcționalitatea lor. Iată o listă (neexhaustivă) a categoriilor de iteratori:

Categorie Limbi
Iterator bidirecțional C ++
Iterator înainte C ++
Iterator de intrare C ++
Iterator de ieșire C ++
Iterator cu acces aleatoriu C ++
Trivial iterator C ++ ( STL vechi )

Tipuri de iteratoare

Diferite limbi sau biblioteci utilizate cu aceste limbi definesc tipurile de iteratoare. Unii dintre ei sunt

Tip Limbi
Iterator de matrice PHP , R
Iterator de cache PHP
Iterator constant C ++ , PHP
Iterator director PHP, Python
Filtru iterator PHP, R
Limitați iteratorul PHP
Listă iterator Java , R
Iterator de matrice recursivă PHP
Iterator XML PHP

În diferite limbaje de programare

C # și alte limbi .NET

Iteratorii din .NET Framework sunt numiți „enumeratori” și reprezentați prin IEnumeratorinterfață. IEnumeratoroferă o MoveNext()metodă, care avansează la următorul element și indică dacă a fost atins sfârșitul colecției; o Currentproprietate, pentru a obține valoarea elementului care este indicat în prezent; și o Reset()metodă opțională , pentru a întoarce enumeratorul înapoi la poziția sa inițială. Enumeratorul indică inițial o valoare specială înainte de primul element, deci MoveNext()este necesar un apel către pentru a începe iterarea.

Enumeratorii se obțin de obicei apelând GetEnumerator()metoda unui obiect care implementează IEnumerableinterfața. Clasele de containere implementează de obicei această interfață. Cu toate acestea, declarația foreach din C # poate funcționa pe orice obiect care furnizează o astfel de metodă, chiar dacă nu se implementează IEnumerable( tastarea rațelor ). Ambele interfețe au fost extinse în versiuni generice în .NET 2.0 .

Următorul arată o utilizare simplă a iteratorilor în C # 2.0:

// explicit version
IEnumerator<MyType> iter = list.GetEnumerator();
while (iter.MoveNext())
    Console.WriteLine(iter.Current);

// implicit version
foreach (MyType value in list)
    Console.WriteLine(value);

C # 2.0 acceptă, de asemenea, generatoare : o metodă care este declarată ca returnând IEnumerator(sau IEnumerable), dar utilizează yield returninstrucțiunea " " pentru a produce o secvență de elemente în loc să returneze o instanță obiect, va fi transformată de compilator într-o nouă clasă care implementează interfața corespunzătoare .

C ++

C ++ limbaj face utilizarea pe scară largă a iteratorii în sale Biblioteca Standard și descrie mai multe categorii de iteratorii diferite în repertoriul operațiunilor care le permit. Acestea includ iteratorii forward , iteratorii bidirecționali și iteratorii cu acces aleatoriu , în ordinea posibilităților în creștere. Toate tipurile standard de șabloane de containere oferă iteratori ai uneia dintre aceste categorii. Iteratorii generalizează pointerele către elementele unui tablou (care într-adevăr pot fi folosite ca iteratoare), iar sintaxa lor este concepută pentru a semăna cu cea a aritmeticii indicatorului C , unde operatorii și sunt utilizați pentru a face referire la elementul la care indică iteratorul și operatorii aritmetici ai pointerului like sunt folosite pentru a modifica iteratorii în traversarea unui container. *->++

Utilizarea transversală a iteratorilor implică de obicei un singur iterator diferit și doi iteratori fixi care servesc la delimitarea unui interval care trebuie parcurs. Distanța dintre iteratorii limitativi, în ceea ce privește numărul de aplicații ale operatorului ++necesare pentru a transforma limita inferioară în cea superioară, este egal cu numărul de articole din intervalul desemnat; numărul de valori de iterator distinct implicate este cu unul mai mult decât atât. Prin convenție, iteratorul limitativ inferior „indică” primul element din interval, în timp ce iteratorul limitativ superior nu indică niciun element din interval, ci mai degrabă chiar dincolo de sfârșitul intervalului. Pentru traversarea unui întreg container, begin()metoda oferă limita inferioară și end()limita superioară. Acesta din urmă nu face referință deloc la niciun element al containerului, ci este o valoare de iterator validă care poate fi comparată cu.

Următorul exemplu arată o utilizare tipică a unui iterator.

std::vector<int> items;
items.push_back(5); // Append integer value '5' to vector 'items'.
items.push_back(2); // Append integer value '2' to vector 'items'.
items.push_back(9); // Append integer value '9' to vector 'items'.

for (auto it = items.begin(); it != items.end(); ++it) { // Iterate through 'items'.
  std::cout << *it; // And print value of 'items' for current index.
}
// In C++11, the same can be done without using any iterators:
for (auto x : items) {
  std::cout << x; // Print value of each element 'x' of 'items'.
}

// Each loops print "529".

Tipurile de iteratoare sunt separate de tipurile de containere cu care sunt folosite, deși cele două sunt adesea utilizate în concert. Categoria iteratorului (și, prin urmare, operațiunile definite pentru acesta) depinde de obicei de tipul de container, de exemplu, matrici sau vectori care furnizează iteratori cu acces aleatoriu, dar seturile (care utilizează o structură legată ca implementare) furnizează numai iteratori bidirecționali. Un același tip de container poate avea mai multe tipuri de iterator asociate; de exemplu, std::vector<T>tipul containerului permite traversarea folosind pointeri (bruti) către elementele sale (de tip *<T>), sau valori de un tip special std::vector<T>::iterator, și încă un alt tip este prevăzut pentru "iteratori inversi", ale căror operațiuni sunt definite în așa fel încât un algoritmul care efectuează o traversare obișnuită (înainte) va efectua de fapt traversarea în ordine inversă atunci când este apelat cu iteratori inversi. Majoritatea containerelor oferă, de asemenea, un const_iteratortip separat , pentru care operațiunile care ar permite schimbarea valorilor arătate nu sunt definite intenționat.

Trecerea simplă a unui obiect container sau a unei game de elemente ale acestuia (inclusiv modificarea acestor elemente, cu excepția cazului în care const_iteratorse utilizează un), se poate face folosind singur iteratori. Dar tipurile de containere pot oferi, de asemenea, metode precum insertsau erasecare modifică structura containerului în sine; acestea sunt metode ale clasei de containere, dar în plus necesită una sau mai multe valori iteratoare pentru a specifica operația dorită. În timp ce este posibil să existe mai mulți iteratori care indică simultan același container, operațiunile de modificare a structurii pot invalida anumite valori ale iteratorului (standardul specifică pentru fiecare caz dacă acest lucru poate fi așa); utilizarea unui iterator invalidat este o eroare care va duce la un comportament nedefinit, iar astfel de erori nu trebuie semnalate de sistemul de timp de rulare.

Iterația implicită este, de asemenea, parțial acceptată de C ++ prin utilizarea șabloanelor de funcții standard, cum ar fi std::for_each(), std::copy() și std::accumulate().

Când sunt utilizate, acestea trebuie inițializate cu iteratorii existenți, de obicei beginși end, care definesc intervalul în care are loc iterația. Dar niciun obiect iterator explicit nu este expus ulterior pe măsură ce iterația continuă. Acest exemplu arată utilizarea for_each.

ContainerType<ItemType> c; // Any standard container type of ItemType elements.

void ProcessItem(const ItemType& i) { // Function that will process each item of the collection.
  std::cout << i << std::endl;
}

std::for_each(c.begin(), c.end(), ProcessItem); // A for-each iteration loop.

Același lucru poate fi realizat folosind std::copy, trecând o std::ostream_iteratorvaloare ca al treilea iterator:

std::copy(c.begin(), c.end(), std::ostream_iterator<ItemType>(std::cout, "\n"));

De la C ++ 11 , sintaxa funcției lambda poate fi utilizată pentru a specifica operația care va fi iterată în linie, evitând necesitatea definirii unei funcții numite. Iată un exemplu pentru fiecare iterație folosind o funcție lambda:

ContainerType<ItemType> c; // Any standard container type of ItemType elements.

// A for-each iteration loop with a lambda function.
std::for_each(c.begin(), c.end(), [](const ItemType& i) { std::cout << i << std::endl; });

Java

Introdusă în versiunea Java JDK 1.2, java.util.Iteratorinterfața permite iterarea claselor de containere. Fiecare Iteratoroferă next()și hasNext()metodă, și poate susține în mod opțional o remove()metodă. Iteratorii sunt creați de clasa de containere corespunzătoare, de obicei printr-o metodă numită iterator().

next()Metoda avansează iteratorul și returnează valoarea indicat de iteratorului. Primul element este obținut la prima chemare la next(). Pentru a determina când au fost vizitate toate elementele din container, hasNext()se folosește metoda de testare. Următorul exemplu arată o utilizare simplă a iteratorilor:

Iterator iter = list.iterator();
// Iterator<MyType> iter = list.iterator(); // in J2SE 5.0
while (iter.hasNext()) {
    System.out.print(iter.next());
    if (iter.hasNext())
        System.out.print(", ");
}

Pentru a arăta că se hasNext()poate apela în mod repetat, îl folosim pentru a insera virgule între elemente, dar nu după ultimul element.

Această abordare nu separă în mod corespunzător operațiunea avansată de accesul efectiv la date. Dacă elementul de date trebuie utilizat de mai multe ori pentru fiecare avans, acesta trebuie stocat într-o variabilă temporară. Când este nevoie de un avans fără acces la date (adică pentru a sări peste un anumit element de date), accesul este totuși efectuat, deși valoarea returnată este ignorată în acest caz.

Pentru tipurile de colectare care o susțin, remove()metoda iteratorului elimină cel mai recent element vizitat din container, păstrând în același timp iteratorul utilizabil. Adăugarea sau eliminarea elementelor apelând metodele containerului (de asemenea, din același fir ) face iteratorul inutilizabil. O încercare de a obține următorul element aruncă excepția. O excepție este, de asemenea, aruncată dacă nu mai sunt elemente rămase ( hasNext()a returnat anterior fals).

În plus, java.util.Listexistă un java.util.ListIteratorAPI cu un API similar, dar care permite iterația înainte și înapoi, oferă indexul curent în listă și permite setarea elementului listă în poziția sa.

Lansarea J2SE 5.0 a Java a introdus Iterableinterfața pentru a suporta o buclă îmbunătățită for( foreach ) pentru iterarea peste colecții și matrice. Iterabledefinește iterator()metoda care returnează un Iterator. Folosind forbucla îmbunătățită , exemplul precedent poate fi rescris ca

for (MyType obj : list) {
    System.out.print(obj);
}

Unele containere folosesc, de asemenea, Enumerationclasa mai veche (de la 1.0) . Acesta oferă hasMoreElements()și nextElement()metode, dar nu are metode de modificare a containerului.

Scala

În Scala , iteratorii au un set bogat de metode similare colecțiilor și pot fi folosiți direct în bucle. Într-adevăr, atât iteratorii, cât și colecțiile moștenesc dintr-o trăsătură de bază comună - scala.collection.TraversableOnce. Cu toate acestea, din cauza set bogat de metode disponibile în colecțiile bibliotecii Scala, cum ar fi map, collect, filteretc, este de multe ori nu este necesar să se ocupe de iteratorii direct la programarea în Scala.

Iteratorii și colecțiile Java pot fi convertite automat în iteratori și colecții Scala, respectiv, pur și simplu prin adăugarea unei singure linii

import scala.collection.JavaConversions._

la dosar. JavaConversionsObiectul oferă conversii implicite pentru a face acest lucru. Conversiile implicite sunt o caracteristică a Scala: metode care, atunci când sunt vizibile în domeniul de aplicare curent, inserează automat apeluri către ele însele în expresii relevante la locul adecvat pentru a le face verificarea tipografică atunci când altfel nu ar face.

MATLAB

MATLAB acceptă atât iterația implicită externă, cât și cea internă, utilizând fie matrici „native”, fie cellmatrici. În cazul unei iterații externe, în care sarcina este asupra utilizatorului de a avansa traversarea și de a solicita următoarele elemente, se poate defini un set de elemente într-o structură de stocare a matricei și poate traversa elementele utilizând forconstrucția -loop. De exemplu,

% Define an array of integers
myArray = [1,3,5,7,11,13];

for n = myArray
   % ... do something with n
   disp(n)  % Echo integer to Command Window
end

parcurge o serie de numere întregi folosind forcuvântul cheie.

În cazul iterației interne în care utilizatorul poate furniza o operațiune iteratorului pentru a efectua peste fiecare element al unei colecții, mulți operatori încorporați și funcții MATLAB sunt suprasolicitate pentru a executa peste fiecare element al unui tablou și a returna implicit un tablou de ieșire corespunzător. . Mai mult, funcțiile arrayfunși cellfunpot fi folosite pentru efectuarea operațiunilor personalizate sau definite de utilizator pe tablouri „native” și cellrespectiv pe tablouri. De exemplu,

function simpleFun
% Define an array of integers
myArray = [1,3,5,7,11,13];

% Perform a custom operation over each element 
myNewArray = arrayfun(@(a)myCustomFun(a),myArray);

% Echo resulting array to Command Window
myNewArray

function outScalar = myCustomFun(inScalar)
% Simply multiply by 2
outScalar = 2*inScalar;

definește o funcție primară simpleFuncare aplică implicit subfuncția personalizată myCustomFunfiecărui element al unui tablou utilizând funcția încorporată arrayfun.

Alternativ, poate fi de dorit să abstracteze de la utilizator mecanismele containerului de stocare a matricii prin definirea unei implementări MATLAB personalizate orientate pe obiecte a modelului Iterator. O astfel de implementare care sprijină iterația externă este demonstrată în MATLAB Central File Exchange Item Design Pattern: Iterator (Behavioral) . Aceasta este scrisă în noua sintaxă de definiție a clasei introdusă cu software-ul MATLAB versiunea 7.6 (R2008a) și prezintă o cellrealizare matricială unidimensională a List Abstract Data Type (ADT) ca mecanism pentru stocarea unui set eterogen (în tip de date) elemente. Acesta oferă funcționalitatea de explicită înainte Lista parcurgeri cu hasNext(), next()și reset()metodele de utilizare într - un while-loop.

PHP

PHP e foreachbuclă a fost introdusă în versiunea 4.0 și compatibil cu obiecte ca valori Cu toate acestea, a fost adăugat suport pentru iterators în PHP 5 prin introducerea intern 4.0 Beta 4. Traversableinterfață. Cele două interfețe principale pentru implementarea în scripturi PHP care permit iterarea obiectelor prin foreachbuclă sunt Iteratorși IteratorAggregate. Acesta din urmă nu necesită ca clasa de implementare să declare toate metodele necesare, ci implementează o metodă accesor ( getIterator) care returnează o instanță de Traversable. Standard PHP Biblioteca oferă mai multe clase pentru a lucra cu iteratori speciale. PHP acceptă, de asemenea, Generatoare de la 5.5.

Cea mai simplă implementare este prin împachetarea unui tablou, acest lucru poate fi util pentru sugestii de tip și ascunderea informațiilor .

namespace Wikipedia\Iterator;

final class ArrayIterator extends \Iterator
{
    private array $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 "key: {$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 'valid: ', ($valid ? 'true' : 'false'), PHP_EOL;
        return $valid;
    }
}

Toate metodele clasei de exemplu sunt utilizate în timpul executării unei bucle complete pentru fiecare ( foreach ($iterator as $key => $current) {}). Metodele iteratorului sunt executate în următoarea ordine:

  1. $iterator->rewind() asigură că structura internă începe de la început.
  2. $iterator->valid()returnează adevărat în acest exemplu.
  3. $iterator->current()valoarea returnată este stocată în $value.
  4. $iterator->key()valoarea returnată este stocată în $key.
  5. $iterator->next() avansează la următorul element din structura internă.
  6. $iterator->valid()returnează fals și bucla este întreruptă.

Următorul exemplu ilustrează o clasă PHP care implementează Traversableinterfața, care ar putea fi înfășurată într-o IteratorIteratorclasă pentru a acționa asupra datelor înainte de a fi returnate la foreachbuclă. Utilizarea împreună cu MYSQLI_USE_RESULTconstanta permite scripturilor PHP să itereze seturile de rezultate cu miliarde de rânduri cu foarte puțină utilizare a memoriei. Aceste caracteristici nu sunt exclusive PHP și nici implementările clasei MySQL (de exemplu, PDOStatementclasa implementează și Traversableinterfața).

mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new \mysqli('host.example.com', 'username', 'password', 'database_name');

// The \mysqli_result class that is returned by the method call implements the internal Traversable interface.
foreach ($mysqli->query('SELECT `a`, `b`, `c` FROM `table`', MYSQLI_USE_RESULT) as $row) {
    // Act on the returned row, which is an associative array.
}

Piton

Iteratorii din Python sunt o parte fundamentală a limbajului și, în multe cazuri, sunt nevăzute, deoarece sunt implicit utilizate în declarația for( foreach ), în înțelegerile listelor și în expresiile generatorului . Toate tipurile standard de colecție încorporate ale Python acceptă iterația, precum și multe clase care fac parte din biblioteca standard. Următorul exemplu prezintă iterația implicită tipică asupra unei secvențe:

for value in sequence:
    print(value)

Dicționarele Python (o formă de matrice asociativă ) pot fi, de asemenea, repetate direct, atunci când cheile de dicționar sunt returnate; sau items()metoda unui dicționar poate fi iterată în cazul în care produce chei corespunzătoare, perechi de valori ca un tuplu:

for key in dictionary:
    value = dictionary[key]
    print(key, value)
for key, value in dictionary.items():
    print(key, value)

Cu toate acestea, iteratorii pot fi utilizați și definiți în mod explicit. Pentru orice tip sau clasă de secvență iterabilă, funcția încorporată iter()este utilizată pentru a crea un obiect iterator. Obiectul iterator poate fi apoi iterat cu next()funcția, care folosește __next__()metoda intern, care returnează următorul element din container. (Afirmația anterioară se aplică Python 3.x. În Python 2.x, next()metoda este echivalentă.) O StopIterationexcepție va fi ridicată atunci când nu vor mai rămâne elemente. Următorul exemplu arată o iterație echivalentă peste o secvență folosind iteratori expliciți:

it = iter(sequence)
while True:
    try:
        value = it.next() # in Python 2.x
        value = next(it) # in Python 3.x
    except StopIteration:
        break
    print(value)

Orice clasă definită de utilizator poate suporta iterația standard (fie implicită, fie explicită) prin definirea unei __iter__()metode care returnează un obiect iterator. Obiectul iterator trebuie apoi să definească o __next__()metodă care returnează următorul element.

Generatorii Python implementează acest protocol de iterație .

Raku

Iteratorii din Raku sunt o parte fundamentală a limbajului, deși de obicei utilizatorii nu trebuie să se preocupe de iteratori. Utilizarea lor este ascunsă în spatele iterație API - uri , cum ar fi fordeclarația, map, grep, lista de indexare cu .[$idx]etc.

Următorul exemplu prezintă iterația implicită tipică asupra unei colecții de valori:

my @values = 1, 2, 3;
for @values -> $value {
    say $value
}
# OUTPUT:
# 1
# 2
# 3

Hash-urile Raku pot fi, de asemenea, repetate direct; aceasta produce Pairobiecte cheie-valoare . kvMetoda poate fi invocată pe hash itera cheie și valori; keysmetoda de a itera cheile hash a lui; și valuesmetoda de a itera peste valorile hash.

my %word-to-number = 'one' => 1, 'two' => 2, 'three' => 3;
for %word-to-number -> $pair {
    say $pair;
}
# OUTPUT:
# three => 3
# one => 1
# two => 2

for %word-to-number.kv -> $key, $value {
    say "$key: $value" 
}
# OUTPUT:
# three: 3
# one: 1
# two: 2

for %word-to-number.keys -> $key {
    say "$key => " ~ %word-to-number{$key};
}
# OUTPUT:
# three => 3
# one => 1
# two => 2

Cu toate acestea, iteratorii pot fi utilizați și definiți în mod explicit. Pentru orice tip iterabil, există mai multe metode care controlează diferite aspecte ale procesului de iterație. De exemplu, iteratormetoda ar trebui să returneze un Iteratorobiect, iar pull-onemetoda ar trebui să producă și să returneze următoarea valoare, dacă este posibil, sau să returneze valoarea santinelă IterationEnddacă nu s-ar putea produce mai multe valori. Următorul exemplu arată o iterație echivalentă asupra unei colecții folosind iteratori expliciți:

my @values = 1, 2, 3;
my $it := @values.iterator;          # grab iterator for @values

loop {
    my $value := $it.pull-one;       # grab iteration's next value
    last if $value =:= IterationEnd; # stop if we reached iteration's end
    say $value;
}
# OUTPUT:
# 1
# 2
# 3

Toate tipurile iterabile din Raku compun Iterablerolul, Iteratorrolul sau ambele. Este Iterabledestul de simplu și necesită doar iteratorimplementarea de către clasa de compunere. Este Iteratormai complex și oferă o serie de metode, cum ar fi pull-one, care permite o operațiune mai fină de iterație în mai multe contexte, cum ar fi adăugarea sau eliminarea articolelor, sau sărind peste ele pentru a accesa alte elemente. Astfel, orice clasă definită de utilizator poate suporta iterația standard prin compunerea acestor roluri și implementarea metodelor iteratorși / sau pull-one.

DNAClasa reprezintă o catenă de ADN și implementează iteratorprin compunerea Iterablerolului. Catenă ADN este împărțită într-un grup de trinucleotide atunci când este iterată peste:

subset Strand of Str where { .match(/^^ <[ACGT]>+ $$/) and .chars %% 3 };
class DNA does Iterable {
    has $.chain;
    method new(Strand:D $chain) {
        self.bless: :$chain
    }
 
    method iterator(DNA:D:){ $.chain.comb.rotor(3).iterator }
};

for DNA.new('GATTACATA') {
    .say
}
# OUTPUT:
# (G A T)
# (T A C)
# (A T A)

say DNA.new('GATTACATA').map(*.join).join('-');
# OUTPUT:
# GAT-TAC-ATA

De Repeaterclasă compune atât Iterableși Iteratorroluri:

class Repeater does Iterable does Iterator {
    has Any $.item  is required;
    has Int $.times is required;
    has Int $!count = 1;
    
    multi method new($item, $times) {
        self.bless: :$item, :$times;
    }
    
    method iterator { self }
    method pull-one(--> Mu){ 
        if $!count <= $!times {
            $!count += 1;
            return $!item
        }
        else {
            return IterationEnd
        }
    }
}

for Repeater.new("Hello", 3) {
    .say
}

# OUTPUT:
# Hello
# Hello
# Hello

Rubin

Ruby implementează iteratorii destul de diferit; toate iterațiile se fac prin trecerea închiderilor de apel invers către metodele containerului - în acest fel Ruby nu numai că implementează iterația de bază, ci și mai multe modele de iterație, cum ar fi maparea funcției, filtrele și reducerea. Ruby acceptă, de asemenea, o sintaxă alternativă pentru metoda de iterație de bază each, următoarele trei exemple sunt echivalente:

(0...42).each do |n|
  puts n
end

...și...

for n in 0...42
  puts n
end

sau chiar mai scurt

42.times do |n|
  puts n
end

Ruby poate itera, de asemenea, peste liste fixe folosind Enumerators și fie apelând #nextmetoda lor , fie făcând câte o pentru fiecare pe ele, ca mai sus.

Rugini

Cu Rust se poate itera elementul vectorilor, sau poate crea iteratori proprii. Fiecare iterator are adaptoare ( map, filter, skip, take, ...).

for n in 0..42 {
    println!("{}", n);
}

Sub fibonacci()funcție se întoarce un iterator personalizat.

for i in fibonacci().skip(4).take(4) {
    println!("{}", i);
}

Vezi si

Referințe

linkuri externe