Operații de agregare
O operație de agregare transformă o colecție întreagă într-o singură valoare. Exemple sunt numărul de elemente, suma lor, cel mai mare element sau toate elementele combinate într-un singur rezultat.
Go îți dă direct câteva dintre acestea. len() numără elementele. slices.Min() și slices.Max() din pachetul slices găsesc cel mai mic și cel mai mare element. Pentru sumă sau medie nu există funcție predefinită: aduni elementele într-o buclă for.
Ai grijă: slices.Min() și slices.Max() dau panic (opresc programul cu eroare) pe un slice gol. Verifică întâi len(s) > 0 dacă slice-ul ar putea fi gol.
Multe limbaje au metode fold și reduce pentru a combina elementele unul câte unul. Go nu are astfel de metode pe slice-uri. În schimb, scrii o singură dată un mic helper generic și îl refolosești. Helperul Fold de mai jos funcționează pentru un slice de orice tip.
Sintaxa helperului generic Fold
T este tipul elementului, iar R este tipul rezultatului. acc este acumulatorul: pornește de la initial, iar f îl combină pe rând cu fiecare element. Ultima valoare a lui acc este rezultatul.
func Fold[T, R any](items []T, initial R, f func(R, T) R) R {
acc := initial
for _, v := range items {
acc = f(acc, v)
}
return acc
}
Agregări de bază
Cele mai des folosite agregări sunt numărul de elemente, minimul, maximul, suma și media.
len(numbers)dă numărul de elemente.slices.Min(numbers)șislices.Max(numbers)dau cel mai mic și cel mai mare element. Funcționează pentru numere și pentru șiruri de caractere.- Suma este o buclă care adună fiecare element într-o variabilă. Media este suma împărțită la numărul de elemente. Convertește-le întâi pe amândouă la
float64, altfel împărțirea aruncă partea zecimală.
Uneori, cel mai mic sau cel mai mare element trebuie ales după o regulă, nu după valoarea în sine. slices.MinFunc() și slices.MaxFunc() primesc o funcție de comparare. Funcția primește două elemente și returnează un număr negativ, zero sau un număr pozitiv. cmp.Compare() din pachetul cmp scrie asta pentru tine.
Uneori, fiecare element trebuie transformat întâi cu o funcție, iar rezultatele trebuie adunate. Go nu are o funcție predefinită pentru asta, așa că exemplul folosește un mic helper generic SumOf.
Număr de elemente, min, max, sumă și medie
Pentru numerele 6, 42, 10 și 4, afișează numărul de elemente cu len(), cel mai mare și cel mai mic element cu slices.Max() și slices.Min(), iar suma dintr-o buclă. Media este 62 / 4 = 15.5.
Ieșire: program Go și rezultatul capturat
package main
import (
"fmt"
"slices"
)
func main() {
numbers := []int{6, 42, 10, 4}
// Go nu are Sum(): aduni elementele într-o buclă.
sum := 0
for _, n := range numbers {
sum += n
}
fmt.Println("Număr:", len(numbers))
fmt.Println("Max:", slices.Max(numbers))
fmt.Println("Min:", slices.Min(numbers))
fmt.Println("Medie:", float64(sum)/float64(len(numbers)))
fmt.Println("Sumă:", sum)
}
Min și max după o regulă
slices.MinFunc() compară resturile a%3 și b%3. Resturile numerelor 5, 42, 10 și 4 sunt 2, 0, 1 și 1, deci câștigă 42. slices.MaxFunc() compară lungimile șirurilor de caractere și găsește "three".
Ieșire: program Go și rezultatul capturat
package main
import (
"cmp"
"fmt"
"slices"
)
func main() {
numbers := []int{5, 42, 10, 4}
strings := []string{"one", "two", "three", "four"}
// Elementul al cărui rest la împărțirea cu 3 este cel mai mic.
minRem := slices.MinFunc(numbers, func(a, b int) int {
return cmp.Compare(a%3, b%3)
})
fmt.Println(minRem)
// Cel mai lung șir de caractere, comparat după lungime.
longest := slices.MaxFunc(strings, func(a, b string) int {
return cmp.Compare(len(a), len(b))
})
fmt.Println(longest)
}
Suma unei funcții aplicate fiecărui element
SumOf aplică funcția pe fiecare element și adună rezultatele. Dublarea numerelor 5, 42, 10 și 4 dă 122. Înjumătățirea lor ca float dă 30.5.
Ieșire: program Go și rezultatul capturat
package main
import "fmt"
// Number este orice tip care poate fi adunat cu +.
type Number interface {
~int | ~float64
}
// SumOf transformă fiecare element într-un număr cu f și adună rezultatele.
func SumOf[T any, N Number](items []T, f func(T) N) N {
var total N
for _, v := range items {
total += f(v)
}
return total
}
func main() {
numbers := []int{5, 42, 10, 4}
fmt.Println(SumOf(numbers, func(n int) int { return n * 2 }))
fmt.Println(SumOf(numbers, func(n int) float64 { return float64(n) / 2 }))
}
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.
Diferența este valoarea de start:
Foldpornește de la o valoare inițială pe care i-o dai tu. Funcția rulează o dată pentru fiecare element.Reducepornește de la primul element. Funcția rulează începând cu al doilea element, deci primul element nu trece niciodată prin ea.
Asta contează atunci când funcția modifică elementul. Dacă ea dublează fiecare element, Reduce lasă primul element așa cum este.
Go nu are nici fold, nici reduce predefinite. Amândouă sunt mici helpere generice cu o buclă for înăuntru. Ca să faci fold de la dreapta, bucla pornește pur și simplu de la ultimul index și coboară până la 0.
Reduce
Reduce ia 5 ca start și adună la el 2, 10 și 4. Rezultatul este 21.
Ieșire: program Go și rezultatul capturat
package main
import "fmt"
// Reduce pornește de la primul element și combină restul în el.
func Reduce[T any](items []T, f func(T, T) T) T {
acc := items[0]
for _, v := range items[1:] {
acc = f(acc, v)
}
return acc
}
func main() {
numbers := []int{5, 2, 10, 4}
sum := Reduce(numbers, func(sum, element int) int { return sum + element })
fmt.Println(sum)
}
Fold
Fold pornește de la 0 și adună fiecare element înmulțit cu 2. Fiecare element este dublat: 10 + 4 + 20 + 8 = 42.
Ieșire: program Go și rezultatul capturat
package main
import "fmt"
// Fold pornește de la o valoare inițială și combină fiecare element în ea.
func Fold[T, R any](items []T, initial R, f func(R, T) R) R {
acc := initial
for _, v := range items {
acc = f(acc, v)
}
return acc
}
func main() {
numbers := []int{5, 2, 10, 4}
sumDoubled := Fold(numbers, 0, func(sum, element int) int { return sum + element*2 })
fmt.Println(sumDoubled)
}
Fold față de Reduce
Amândouă helperele folosesc aceeași funcție, suma plus elementul înmulțit cu 2. Reduce dă 37, pentru că primul element, 5, este startul și nu este dublat. Fold cu valoarea inițială 0 dă 42.
Ieșire: program Go și rezultatul capturat
package main
import "fmt"
// Reduce folosește primul element ca start, deci f nu este aplicată niciodată pe el.
func Reduce[T any](items []T, f func(T, T) T) T {
acc := items[0]
for _, v := range items[1:] {
acc = f(acc, v)
}
return acc
}
// Fold folosește initial ca start, deci f este aplicată pe fiecare element.
func Fold[T, R any](items []T, initial R, f func(R, T) R) R {
acc := initial
for _, v := range items {
acc = f(acc, v)
}
return acc
}
func main() {
numbers := []int{5, 2, 10, 4}
// 5 este aici valoarea de start, deci nu este dublat: 5 + 4 + 20 + 8
reduced := Reduce(numbers, func(sum, element int) int { return sum + element*2 })
fmt.Println("Reduce:", reduced)
// Fiecare element este dublat: 10 + 4 + 20 + 8
folded := Fold(numbers, 0, func(sum, element int) int { return sum + element*2 })
fmt.Println("Fold:", folded)
}
Fold de la dreapta
FoldRight parcurge slice-ul de la ultimul element spre primul. Adunarea nu ține cont de ordine, așa că rezultatul este din nou 42.
Ieșire: program Go și rezultatul capturat
package main
import "fmt"
// FoldRight este ca Fold, dar parcurge slice-ul de la ultimul element spre primul.
func FoldRight[T, R any](items []T, initial R, f func(T, R) R) R {
acc := initial
for i := len(items) - 1; i >= 0; i-- {
acc = f(items[i], acc)
}
return acc
}
func main() {
numbers := []int{5, 2, 10, 4}
sumDoubledRight := FoldRight(numbers, 0, func(element, sum int) int { return sum + element*2 })
fmt.Println(sumDoubledRight)
}
Fold cu index
Uneori contează poziția unui element, nu doar valoarea lui.
În Go, o buclă simplă îți dă deja indexul. for idx, element := range numbers merge de la început. for idx := len(numbers) - 1; idx >= 0; idx-- merge de la sfârșit. În interiorul buclei, un if verifică indexul și decide dacă elementul este adunat.
Fold cu index
Sunt adunate doar elementele de la indexuri pare: indexul 0 conține 5, iar indexul 2 conține 10, deci suma este 15.
Ieșire: program Go și rezultatul capturat
package main
import "fmt"
func main() {
numbers := []int{5, 2, 10, 4}
// range dă și indexul, așa că îl poți folosi în condiție.
sumEven := 0
for idx, element := range numbers {
if idx%2 == 0 {
sumEven += element
}
}
fmt.Println(sumEven)
}
Fold cu index de la dreapta
Aceeași regulă, dar bucla pornește de la indexul 3 și coboară până la 0. Rezultatul este tot 15.
Ieșire: program Go și rezultatul capturat
package main
import "fmt"
func main() {
numbers := []int{5, 2, 10, 4}
// Mergi de la ultimul index până la 0 și aduni elementele de la indexuri pare.
sumEven := 0
for idx := len(numbers) - 1; idx >= 0; idx-- {
if idx%2 == 0 {
sumEven += numbers[idx]
}
}
fmt.Println(sumEven)
}
Reduce în siguranță
Reduce are nevoie de un prim element de la care să pornească. Pe un slice gol, items[0] dă panic și programul se oprește cu eroare.
Go nu are null pentru numere. Modul în care se face în Go este să returnezi două valori: rezultatul și un bool numit ok. Când slice-ul este gol, ok este false, iar rezultatul este valoarea zero a tipului, de exemplu 0 pentru int. Este același tipar value, ok pe care îl știi de la map-uri.
Reduce cu o valoare ok
ReduceOK returnează 21 true pentru numerele 5, 2, 10 și 4 și 0 false pentru un slice gol.
Ieșire: program Go și rezultatul capturat
package main
import "fmt"
// ReduceOK returnează ok = false în loc să dea panic pe un slice gol.
func ReduceOK[T any](items []T, f func(T, T) T) (T, bool) {
var zero T
if len(items) == 0 {
return zero, false
}
acc := items[0]
for _, v := range items[1:] {
acc = f(acc, v)
}
return acc, true
}
func main() {
numbers := []int{5, 2, 10, 4}
empty := []int{}
sum, ok := ReduceOK(numbers, func(sum, element int) int { return sum + element })
fmt.Println(sum, ok)
sum, ok = ReduceOK(empty, func(sum, element int) int { return sum + element })
fmt.Println(sum, ok)
}
Fold și Reduce cu valori parțiale
Un fold sau un reduce cu valori parțiale nu aruncă pașii intermediari. El salvează acumulatorul după fiecare element, deci rezultatul este un slice cu toate valorile parțiale.
În Go ții o variabilă pentru sumă și o adaugi cu append() într-un slice de rezultate, în interiorul buclei. Pentru un reduce cu valori parțiale, rezultatul începe cu primul element. Pentru un fold cu valori parțiale, rezultatul începe cu valoarea inițială.
Reduce cu valori parțiale
Numerele de la 0 la 5 sunt adunate unul câte unul. Slice-ul rezultat conține fiecare sumă de pe parcurs: [0 1 3 6 10 15].
Ieșire: program Go și rezultatul capturat
package main
import "fmt"
func main() {
numbers := []int{0, 1, 2, 3, 4, 5}
// Primul element este startul; fiecare pas următor este salvat în rezultat.
sum := numbers[0]
runningReduceSum := []int{sum}
for _, item := range numbers[1:] {
sum += item
runningReduceSum = append(runningReduceSum, sum)
}
fmt.Println(runningReduceSum)
}
Fold cu valori parțiale
La fel, dar cu valoarea inițială 10. Rezultatul începe chiar cu 10 și are cu un element mai mult decât intrarea: [10 10 11 13 16 20 25].
Ieșire: program Go și rezultatul capturat
package main
import "fmt"
func main() {
numbers := []int{0, 1, 2, 3, 4, 5}
// Valoarea inițială 10 este primul element al rezultatului.
sum := 10
runningFoldSum := []int{sum}
for _, item := range numbers {
sum += item
runningFoldSum = append(runningFoldSum, sum)
}
fmt.Println(runningFoldSum)
}