MySQL Java JavaScript PHP Python HTML-CSS C-sharp C++ Go

Coleții și iteratori

Iteratori

Iteratorii reprezintă o abstractizare pentru parcurgerea seturilor de date și sunt utilizați pentru a organiza accesul secvențial la elementele seturilor de date - cum ar fi matricele, obiectele Set, Map, șirurile etc. Astfel, datorită iteratorilor, putem parcurge un set de date (de exemplu, o matrice) folosind o buclă for-of:

const people = ["Tom", "Bob", "Sam"];
for(const person of people){
   console.log(person);
}

În bucla for-of, în partea dreaptă a operatorului "of", se specifică setul de date sau obiectul iterabil (cunoscut sub numele de Iterable) din care putem obține elemente individuale în buclă. Cu toate acestea, posibilitatea de a parcurge un anumit obiect, cum ar fi o matrice în exemplul de mai sus, este implementată datorită faptului că aceste obiecte utilizează iteratori. Să analizăm mai detaliat ce reprezintă iteratorii și cum putem crea propriul iterator.

Obținerea unui iterator

Orice obiect iterabil (cum ar fi o matrice, Map, Set etc.) conține în proprietatea Symbol.iterator o funcție care returnează iteratorul asociat obiectului:

const people = ["Tom", "Bob", "Sam"];
// Primim iteratorul unei matrice
const iterator = people[Symbol.iterator]();
console.log(iterator);  // Array Iterator {}

Aici obținem iteratorul unei matrice, așa că în consolă va fi afișat ceva similar cu Array Iterator {}

Un alt exemplu - șirul reprezintă, de asemenea, un obiect iterabil, pe care îl putem parcurge caracter cu caracter:

const username = "Tom";
for(char of username){
   console.log(char);
}

Corespunzător, pentru șir, de asemenea, putem obține un iterator:

const username = "Tom";
//Primim iteratorul unui șir
const iterator = username[Symbol.iterator]();
console.log(iterator);  // StringIterator {}

Iteratorul pentru șir reprezintă tipul StringIterator. Similar, putem obține iteratori și pentru alte tipuri de obiecte iterabile.

Este important să menționăm că diverse tipuri pot avea metode suplimentare diferite pentru obținerea iteratorului. De exemplu, pentru matrice există metoda entries(), care, de asemenea, returnează iteratorul matricei:

const people = ["Tom", "Bob", "Sam"];
console.log(people.entries()); // Array Iterator {}

Metoda next() a iteratorilor

Iteratorii furnizează metoda next(), care returnează un obiect cu două proprietăți: value și done.

{value, done}

Proprietatea value conține valoarea efectivă a elementului curent care este iterat. Proprietatea done indică dacă mai există sau nu obiecte disponibile pentru iterare în colecție. Dacă în colecție mai există elemente, atunci proprietatea done este falsă. În caz contrar, dacă nu mai există elemente disponibile pentru iterare, proprietatea done este adevărată, iar metoda next() returnează un obiect.

{done: true}

De exemplu:

const people = ["Tom", "Bob", "Sam"];
const iter = people[Symbol.iterator]();
const result = iter.next();
console.log(result);    // {value: "Tom", done: false}

În acest caz, apelăm metoda next() și obținem primul rezultat din iterator:

{value: "Tom", done: false}

Aici vedem că obiectul curent reprezintă șirul "Tom", iar valoarea done: false indică faptul că în matrice mai există elemente pentru a fi parcurse.

Putem apela în mod consecutiv de mai multe ori metoda next() pentru a obține alte elemente ale matricei:

const people = ["Tom", "Bob", "Sam"];
const iter = people[Symbol.iterator]();
console.log(iter.next());    // {value: "Tom", done: false}
console.log(iter.next());    // {value: "Bob", done: false}
console.log(iter.next());    // {value: "Sam", done: false}
console.log(iter.next());    // {value: undefined, done: true}

Afișajul la consolă :

{ value: 'Tom', done: false }
{ value: 'Jerry', done: false }
{ value: 'Spike', done: false }
{ value: undefined, done: true }

Aici vedem că la fiecare apelare a metodei next() obținem următorul obiect din matrice. Atunci când nu mai există obiecte pentru a fi parcurse, proprietatea done devine true.

Prin utilizarea metodei next(), putem parcurge singuri toate obiectele din matrice:

const people = ["Tom", "Bob", "Sam"];
const iter = people[Symbol.iterator]();
while(!(item = iter.next()).done){
   console.log(item.value);
}

Aici, într-un ciclu while, obținem obiectul curent din metoda next() a iteratorului și îl stocăm în variabila item: `item = items.next()`

Apoi verificăm proprietatea sa done - dacă este egală cu false (adică mai există elemente în set), continuăm ciclul:

while (!(item = iter.next()).done) {

În cadrul ciclului, ne referim la proprietatea value a obiectului obținut:

console.log(item.value);

Output în consolă:

Tom
Bob
Sam

Dar acest lucru nu are sens, deoarece toate colecțiile care returnează iteratori susțin parcurgerea cu ajutorul buclei for...of, care utilizează în mod implicit iteratorul pentru a obține elementele.

Crearea propriului iterator

Pentru exemplificare, să implementăm un iterator care parcurge o matrice de la sfârșit:

const people = ["Tom", "Bob", "Sam"];

function reverseArrayIterator(array) {  
   let count = array.length;
   return {    
       next: function(){      
           if (count > 0) {  
               return {          
                   value: array[--count],          
                   done: false       
               };    
           }
           else {      
               return {          
                   value: undefined,          
                   done: true       
               };          
           }    
       }  
   }
};
const iter = reverseArrayIterator(people);
while(!(item = iter.next()).done){
   console.log(item.value);
}

Aici, mai întâi este inițializată variabila "count", care reprezintă numărul de elemente parcurse dintr-un array. Inițial, variabila are valoarea egală cu lungimea array-ului.

Apoi, funcția returnează un obiect iterator. Metoda sa next() implementează comportamentul iterației: dacă contorul "count" este mai mare de 0 (adică mai există elemente de parcurs), atunci next() returnează un obiect, a cărui proprietate "done" are valoarea false (deoarece iteratorul nu a ajuns încă la sfârșitul sau, mai precis, la începutul array-ului), iar proprietatea "value" conține elementul corespunzător din array, indicat de variabila "count" după decrementare.

Când variabila "count" devine 0 (adică iteratorul a ajuns la sfârșit), next() returnează un obiect cu proprietatea "done" având valoarea true, iar proprietatea "value" având valoarea undefined.

Astfel, obținem un iterator care parcurge obiectele array-ului de la sfârșit. Afișajul în consolă:

Sam
Bob
Tom

Cu toate acestea, în timpul executării buclei for..of, elementele array-ului continuă să fie parcurse de la început. Aplicăm iteratorul nostru global pentru a fi folosit și în bucla for..of:

const people = ["Tom", "Bob", "Sam"];

function reverseArrayIterator() {
   const array = this;
   let count = array.length;
   return {    
       next: function(){      
           if (count > 0) {  
               return {          
                   value: array[--count],          
                   done: false       
               };    
           }
           else {      
               return {          
                   value: undefined,          
                   done: true       
               };          
           }    
       }  
   }
};
// Schimbăm iteratorul pentru array-ul "people"
people[Symbol.iterator]=reverseArrayIterator;
for(person of people){
   console.log(person);
}

Aici sunt făcute două schimbări cheie. În primul rând, trebuie să obținem obiectul curent în interiorul iteratorului prin intermediul lui "this":

const array = this;

Funcția de iterator creată trebuie asignată proprietății Symbol.iterator:

people[Symbol.iterator]=reverseArrayIterator;

Crearea de obiecte iterabile

Diverse obiecte pot avea propria lor implementare a iteratorului. Și, dacă este necesar, putem defini un obiect cu propriul său iterator. Utilizarea iteratorilor ne oferă posibilitatea de a crea un obiect care se va comporta ca o colecție de elemente.

Pentru a crea un obiect iterabil, trebuie să definim în obiect o metodă Symbol.iterator(). Această metodă va reprezenta efectiv iteratorul:

const iterable = {
 [Symbol.iterator]() {
   return {
     next() {
       // dacă mai există elemente
       return { value: ..., done: false };
       // dacă nu mai există elemente
       return { value: undefined, done: true };
     }
   };
 }
};

Metoda [Symbol.iterator]() returnează un obiect care are metoda next(). Această metodă returnează un obiect cu două proprietăți: value și done.

Dacă în obiectul nostru există elemente, proprietatea value conține valoarea efectivă a elementului, iar proprietatea done este setată la false.

Dacă nu mai există elemente disponibile, proprietatea done este setată la true.

De exemplu, vom implementa un obiect simplu cu un iterator care returnează un set de numere:

const iterable = {
 [Symbol.iterator]() {
   return {
     current: 1,
     end: 3,
     next() {
       if (this.current <= this.end) {
         return { value: this.current++, done: false };
       }
       return { done: true };
     }
   };
 }
};

Aici, iteratorul efectiv returnează numere de la 1 la 3. Pentru a urmări elementul curent în obiectul returnat de metoda [Symbol.iterator](), sunt definite două proprietăți:

current: 1,
end: 3,

Proprietatea current păstrează valoarea efectivă a elementului curent. Proprietatea end stabilește limita. Astfel, în acest caz, iteratorul returnează numere de la 1 la 3.

În metoda next(), dacă valoarea curentă este mai mică sau egală cu valoarea limită, returnăm un obiect:

return { value: this.current++, done: false };

Incrementarea this.current++ va face ca la următorul apel al metodei next, valoarea current să fie cu o unitate mai mare.

Dacă limita este atinsă, returnăm un obiect:

return { done: true };

Acest lucru indică faptul că nu mai există obiecte.

Vom obține elementele returnate de iterator:

const myIterator = iterable[Symbol.iterator](); // primim iteratorul
console.log(myIterator.next()); // {value: 1, done: false}
console.log(myIterator.next()); // {value: 2, done: false}
console.log(myIterator.next()); // {value: 3, done: false}
console.log(myIterator.next()); // {done: true}

Aici, mai întâi obținem iteratorul în constanta `myIterator`. Apoi, prin apelarea metodei sale `next()`, obținem în mod consecutiv toate elementele. La al patrulea apel al metodei `next()`, parcurgerea condițională a elementelor în iterator se încheie, iar metoda returnează obiectul {done: true}.

Cu toate acestea, dacă dorim să parcurgem obiectul nostru și să obținem elementele sale, nu este nevoie să apelăm metoda `next()`. Deoarece obiectul `iterable` implementează iteratorul, îl putem parcurge folosind bucla for-of:

const iterable = {
 [Symbol.iterator]() {
   return {
     current: 1,
     end: 3,
     next() {
       if (this.current <= this.end) {
         return { value: this.current++, done: false };
       }
       return { done: true };
     }
   };
 }
};
for (const value of iterable) {
 console.log(value);
}

Outputul în consolă:

1
2
3

Buclele for-of apelează automat metoda next() și extrag valoarea.

Să luăm încă un exemplu:

// Companie
const company = {
   // Vector de angajați
   employees: [
       {name: "Tom", age: 39, position: "Senior Developer"},
       {name: "Bob", age: 43, position: "Middle Developer"},
       {name: "Sam", age: 28, position: "Junior Developer"},
   ]
};
// Setăm iteratorul
company[Symbol.iterator] = function() {
   const array = this.employees; // Obținem vectorul de angajați
   let current = 0;
   return {
       next() {
           if (current < array.length) {
               return { value: array[current++].name, done: false };
           }
           return { value: undefined, done: true };
       }
   };
};

// Pentru fiecare angajat din companie
for (const employee of company) {
   console.log(employee);
}

Aici, obiectul company reprezintă o companie ipotetică, în care există un array de angajați - array-ul employees. Să presupunem că, cu ajutorul unui iterator, dorim să obținem numele fiecărui angajat. Pentru aceasta, pentru obiectul company, stabilim o funcție iterator care parcurge toate elementele din array-ul employees. Afișajul în consolă al programului:

Tom
Bob
Sam