Operații de agregare
O operație de agregare transformă o întreagă colecție într-o singură valoare. Exemple sunt numărul de elemente, suma lor, cel mai mare element sau toate elementele combinate într-un singur rezultat.
Tablourile îți oferă direct câteva dintre acestea. numbers.length numără elementele. Math.max() și Math.min() găsesc cel mai mare și cel mai mic număr; le dai elementele cu Math.max(...numbers). Pentru o sumă sau o medie nu există o metodă: combini elementele cu reduce().
Ai grijă cu tablourile goale. Math.max() fără valori returnează -Infinity, iar Math.min() returnează Infinity, deci verifică numbers.length > 0 atunci când tabloul ar putea fi gol.
Multe limbaje au metode separate fold și reduce pentru a combina elementele unul câte unul. Aici ambele sunt aceeași metodă, reduce(), iar diferența stă doar în faptul că treci sau nu o valoare inițială. reduceRight() face aceeași parcurgere, de la ultimul element spre primul.
Agregări de bază
Cele mai obișnuite agregări sunt numărul de elemente, minimul, maximul, suma și media.
numbers.lengthdă numărul de elemente.Math.max(...numbers)șiMath.min(...numbers)dau cel mai mare și cel mai mic număr. Funcționează doar pentru numere.- Suma este un
reduce()care adaugă fiecare element la un total curent. Media este suma împărțită la numărul de elemente; împărțirea păstrează partea zecimală, deci62 / 4este15.5.
Uneori cel mai mic sau cel mai mare element trebuie ales după o regulă, nu după valoarea în sine. reduce() face și asta: la fiecare pas păstrezi pe cel mai bun dintre cele două elemente. Acest lucru funcționează și pentru cuvinte și obiecte, unde Math.min() nu poate ajuta.
Uneori fiecare element trebuie transformat mai întâi într-un număr, iar rezultatele adunate. Și asta este tot un reduce(): adaugi valoarea calculată în loc de elementul însuși.
Număr de elemente, minim, maxim, sumă și medie
Pentru numerele 6, 42, 10 și 4 programul afișează numărul de elemente cu length, cel mai mare și cel mai mic cu Math.max() și Math.min(), iar suma dintr-un reduce(). Media este 62 / 4 = 15.5.
Ieșire: program TypeScript și rezultatul capturat
const numbers = [6, 42, 10, 4]
// nu există o metodă sum(): reduce() adună elementele
const sum = numbers.reduce((total, n) => total + n, 0)
console.log('Număr:', numbers.length)
console.log('Maxim:', Math.max(...numbers))
console.log('Minim:', Math.min(...numbers))
console.log('Medie:', sum / numbers.length)
console.log('Sumă:', sum)
Minim și maxim după o regulă
Primul reduce() păstrează elementul cu restul mai mic la împărțirea cu 3. Resturile lui 5, 42, 10 și 4 sunt 2, 0, 1 și 1, deci câștigă 42. Al doilea păstrează cuvântul mai lung și găsește "three".
Ieșire: program TypeScript și rezultatul capturat
const numbers = [5, 42, 10, 4]
const words = ['one', 'two', 'three', 'four']
// elementul al cărui rest la împărțirea cu 3 este cel mai mic
const minRemainder = numbers.reduce((best, n) => (n % 3 < best % 3 ? n : best))
console.log(minRemainder)
// cel mai lung cuvânt, comparat după lungime
const longest = words.reduce((best, word) => (word.length > best.length ? word : best))
console.log(longest)
Suma unei funcții aplicate fiecărui element
Callback-ul adaugă la total o valoare calculată, în loc de elementul însuși. Dublarea lui 5, 42, 10 și 4 dă 122. Înjumătățirea lor dă 30.5, pentru că împărțirea păstrează partea zecimală.
Ieșire: program TypeScript și rezultatul capturat
const numbers = [5, 42, 10, 4]
// reduce() adună o valoare calculată din fiecare element
console.log(numbers.reduce((total, n) => total + n * 2, 0))
console.log(numbers.reduce((total, n) => total + n / 2, 0))
Fold și Reduce
Fold și reduce combină toate elementele într-o singură valoare, pas cu pas. Ele păstrează o valoare curentă numită acumulator. Pentru fiecare element apelează o funcție cu acumulatorul și elementul, iar funcția returnează noul acumulator.
Aici ambele sunt aceeași metodă. Diferența este al doilea argument al lui reduce():
reduce(fn, initial)este un fold. Pornește de la valoarea inițială pe care i-o dai, deci funcția rulează o dată pentru fiecare element.reduce(fn)este un reduce. Pornește de la primul element, deci funcția rulează începând cu al doilea element, iar primul element nu trece niciodată prin ea.
Asta contează atunci când funcția modifică elementul. Dacă dublează fiecare element, reduce(fn) lasă primul element așa cum este.
Contează și pentru tablourile goale. reduce(fn, initial) returnează pur și simplu valoarea inițială, în timp ce reduce(fn) nu are de unde să pornească și aruncă un TypeError.
Pentru a combina dinspre celălalt capăt, folosește reduceRight(): aceeași metodă, parcurgând de la ultimul element spre primul.
Reduce
reduce() fără valoare inițială ia 5 ca start și adaugă la el 2, 10 și 4, deci rezultatul este 21. Același apel pe un tablou gol aruncă o eroare, iar programul afișează mesajul pe care aceasta îl poartă: Reduce of empty array with no initial value.
Ieșire: program TypeScript și rezultatul capturat
const numbers = [5, 2, 10, 4]
// fără valoare inițială, reduce() pornește de la primul element
const sum = numbers.reduce((acc, element) => acc + element)
console.log(sum)
// un tablou gol nu are un prim element de la care să pornească
const empty: number[] = []
try {
empty.reduce((acc, element) => acc + element)
} catch (error) {
console.log((error as Error).message)
}
Fold
Aici reduce() primește valoarea inițială 0 și adaugă fiecare element înmulțit cu 2. Fiecare element este dublat: 10 + 4 + 20 + 8 = 42.
Ieșire: program TypeScript și rezultatul capturat
const numbers = [5, 2, 10, 4]
// al doilea argument este valoarea inițială, deci fiecare element trece prin funcție
const sumDoubled = numbers.reduce((sum, element) => sum + element * 2, 0)
console.log(sumDoubled)
Fold față de Reduce
Ambele apeluri folosesc aceeași funcție, suma plus elementul înmulțit cu 2. Fără valoare inițială rezultatul este 37, pentru că primul element, 5, este startul și nu este dublat. Cu valoarea inițială 0 rezultatul este 42.
Ieșire: program TypeScript și rezultatul capturat
const numbers = [5, 2, 10, 4]
// fără valoare inițială: 5 este startul și nu este niciodată dublat, deci 5 + 4 + 20 + 8
const reduced = numbers.reduce((sum, element) => sum + element * 2)
console.log('Reduce:', reduced)
// valoarea inițială 0: fiecare element este dublat, deci 10 + 4 + 20 + 8
const folded = numbers.reduce((sum, element) => sum + element * 2, 0)
console.log('Fold:', folded)
Fold dinspre dreapta
reduceRight() parcurge tabloul de la ultimul element spre primul. Adunării nu îi pasă de ordine, deci rezultatul este din nou 42.
Ieșire: program TypeScript și rezultatul capturat
const numbers = [5, 2, 10, 4]
// reduceRight() parcurge tabloul de la ultimul element spre primul
const sumDoubledRight = numbers.reduceRight((sum, element) => sum + element * 2, 0)
console.log(sumDoubledRight)
Fold cu index
Uneori contează poziția unui element, nu doar valoarea lui.
Callback-ul lui reduce() primește indexul ca al treilea argument, după acumulator și element. O condiție pe acel index decide apoi ce se întâmplă cu elementul. reduceRight() transmite aceiași indecși; doar că îi vizitează dinspre sfârșit.
Fold cu index
Se adună doar elementele de la indecși pari: indexul 0 conține 5, iar indexul 2 conține 10, deci suma este 15.
Ieșire: program TypeScript și rezultatul capturat
const numbers = [5, 2, 10, 4]
// al treilea argument al callback-ului este indexul
const sumEven = numbers.reduce((sum, element, idx) => (idx % 2 === 0 ? sum + element : sum), 0)
console.log(sumEven)
Fold cu index dinspre dreapta
Aceeași regulă cu reduceRight(). Callback-ul vede mai întâi indexul 3 și la final indexul 0, iar rezultatul este tot 15.
Ieșire: program TypeScript și rezultatul capturat
const numbers = [5, 2, 10, 4]
// reduceRight() numără aceiași indecși, doar că îi parcurge de la sfârșit
const sumEven = numbers.reduceRight((sum, element, idx) => (idx % 2 === 0 ? sum + element : sum), 0)
console.log(sumEven)
Reduce în siguranță
reduce() fără valoare inițială are nevoie de un prim element de la care să pornească. Pe un tablou gol aruncă un TypeError și oprește programul.
O soluție este o valoare inițială. Cealaltă este să spui cinstit „nu există niciun rezultat”: verifici mai întâi lungimea și returnezi undefined. Tipul returnat devine atunci number | undefined, ceea ce îl face pe apelant să se gândească la cazul gol, iar ?? pune dintr-un singur pas o valoare de rezervă în locul lui undefined.
Reduce care poate returna undefined
sumOrUndefined() returnează 21 pentru numerele 5, 2, 10 și 4 și undefined pentru un tablou gol. sumOrUndefined(empty) ?? 0 transformă acel undefined în 0.
Ieșire: program TypeScript și rezultatul capturat
// sumOrUndefined răspunde cu undefined în loc să arunce o eroare pentru un tablou gol.
function sumOrUndefined(items: number[]): number | undefined {
if (items.length === 0) return undefined
return items.reduce((sum, element) => sum + element)
}
const numbers = [5, 2, 10, 4]
const empty: number[] = []
console.log(sumOrUndefined(numbers))
console.log(sumOrUndefined(empty))
// ?? pune o valoare de rezervă în locul lui undefined
console.log(sumOrUndefined(empty) ?? 0)
Fold și Reduce parțiale
Un fold sau un reduce parțial nu aruncă pașii intermediari. Păstrează acumulatorul după fiecare element, deci rezultatul este un tablou cu toate valorile parțiale.
Nu există o metodă pentru asta. Ții o variabilă sumă și o adaugi cu push() într-un tablou rezultat, în interiorul buclei. Pentru un reduce parțial rezultatul începe cu primul element. Pentru un fold parțial începe cu valoarea inițială, deci conține cu un element mai mult decât intrarea.
Reduce parțial
Numerele de la 0 la 5 sunt adunate unul câte unul. Tabloul rezultat conține fiecare sumă de pe parcurs și este afișat pe o singură linie cu join(' '): 0 1 3 6 10 15.
Ieșire: program TypeScript și rezultatul capturat
const numbers = [0, 1, 2, 3, 4, 5]
// primul element este startul; fiecare sumă ulterioară este păstrată în rezultat
let sum = numbers[0]
const runningReduceSum = [sum]
for (const item of numbers.slice(1)) {
sum += item
runningReduceSum.push(sum)
}
console.log(runningReduceSum.join(' '))
Fold parțial
Același lucru cu valoarea inițială 10. Rezultatul începe chiar cu 10 și conține cu un element mai mult decât intrarea: 10 10 11 13 16 20 25.
Ieșire: program TypeScript și rezultatul capturat
const numbers = [0, 1, 2, 3, 4, 5]
// valoarea inițială 10 este primul element al rezultatului
let sum = 10
const runningFoldSum = [sum]
for (const item of numbers) {
sum += item
runningFoldSum.push(sum)
}
console.log(runningFoldSum.join(' '))