Gruparea unei colecții
Gruparea înseamnă împărțirea elementelor în grupuri. Alegi o cheie pentru fiecare element, iar toate elementele cu aceeași cheie ajung în același grup.
Cheia este calculată de o funcție mică ce primește un element și returnează cheia lui. De exemplu, prima literă a unui cuvânt.
Go nu are o metodă groupBy integrată pentru slice-uri. Modul natural de a grupa în Go este un map ale cărui chei sunt cheile de grup și ale cărui valori sunt slice-uri de elemente: map[K][]T. Îl umpli cu o buclă for și cu append() sau scrii o singură dată micul helper generic de mai jos și îl refolosești.
Un map nu are o ordine fixă, dar fmt.Println() afișează un map sortat după cheie. Așa că rezultatul afișat arată mereu la fel.
Sintaxa helperului generic GroupBy
T este tipul elementului, iar K este tipul cheii. K trebuie să fie comparable, pentru că cheile unui map trebuie să suporte ==. Pentru fiecare element, helperul apelează key() și adaugă elementul la slice-ul stocat sub acea cheie. Adăugarea la o cheie care nu există încă pornește pur și simplu un slice nou.
func GroupBy[T any, K comparable](items []T, key func(T) K) map[K][]T {
groups := make(map[K][]T)
for _, v := range items {
k := key(v)
groups[k] = append(groups[k], v)
}
return groups
}
Grupare după cheie
Cea mai simplă grupare păstrează elementele așa cum sunt și decide doar în ce grup ajunge fiecare.
Trimite slice-ul și o funcție de cheie helperului GroupBy. Rezultatul este un map de la cheie la slice-ul cu elementele care au acea cheie. Elementele dintr-un grup păstrează ordinea pe care o aveau în datele de intrare.
Uneori vrei să schimbi elementele în timp ce le grupezi, de exemplu să le stochezi cu majuscule. Go nu are un parametru valueTransform pentru asta. Scrie tu bucla: calculezi cheia, calculezi valoarea nouă și adaugi valoarea nouă în grup.
Grupează cuvintele după prima literă
Funcția de cheie ia prima literă a cuvântului cu w[:1] și o transformă în majusculă cu strings.ToUpper(). Cuvintele care încep cu aceeași literă ajung în același slice.
Ieșire: program Go și rezultatul capturat
package main
import (
"fmt"
"strings"
)
// GroupBy pune fiecare element în grupul ales de key().
func GroupBy[T any, K comparable](items []T, key func(T) K) map[K][]T {
groups := make(map[K][]T)
for _, v := range items {
k := key(v)
groups[k] = append(groups[k], v)
}
return groups
}
func main() {
words := []string{"one", "two", "three", "four", "five"}
// Cheia este prima literă a cuvântului, scrisă cu majusculă.
byFirstLetter := GroupBy(words, func(w string) string { return strings.ToUpper(w[:1]) })
fmt.Println(byFirstLetter)
}
Grupează și schimbă valorile
Cheia este prima literă scrisă cu minusculă. Valoarea stocată este cuvântul întreg scris cu majuscule. O buclă for simplă face ambii pași și adaugă la groups[key].
Ieșire: program Go și rezultatul capturat
package main
import (
"fmt"
"strings"
)
func main() {
words := []string{"one", "two", "three", "four", "five"}
groups := make(map[string][]string)
for _, w := range words {
key := w[:1] // prima literă, cu minusculă
value := strings.ToUpper(w) // valoarea stocată în grup
groups[key] = append(groups[key], value)
}
fmt.Println(groups)
}
Numără fiecare grup
Deseori nu ai nevoie de elementele fiecărui grup, ci doar de câte sunt.
Go nu are groupingBy() sau eachCount(). Folosește în schimb un map[string]int. Citirea unei chei care nu se află în map dă 0, deci counts[key]++ funcționează chiar și pentru primul element al unui grup.
Numără cuvintele pentru fiecare primă literă
Fiecare cuvânt adaugă 1 la numărătoarea primei sale litere. Map-ul ajunge să aibă câte un număr pentru fiecare literă.
Ieșire: program Go și rezultatul capturat
package main
import "fmt"
func main() {
words := []string{"one", "two", "three", "four", "five"}
// counts[k] pornește de la 0 pentru o cheie nouă, deci ++ funcționează imediat.
counts := make(map[string]int)
for _, w := range words {
counts[w[:1]]++
}
fmt.Println(counts)
}
Fold și reduce pe fiecare grup
A face fold pe un grup înseamnă a combina elementele lui într-o singură valoare, pas cu pas. Un total curent este exemplul clasic: pornești de la 0 și aduni fiecare element.
Go nu are fold() sau aggregate() pentru grupuri. Modul Go este un map care ține valoarea curentă a fiecărui grup. Pentru fiecare element, calculezi cheia lui și actualizezi valoarea stocată sub acea cheie.
Când valoarea curentă are o valoare de pornire, precum 0 pentru o sumă, o cheie lipsă îți dă deja acel start. Când primul element trebuie el însuși să pornească valoarea, verifici cu tiparul value, ok := m[key] dacă cheia există deja.
Suma fiecărui grup
Fiecare număr este pus în grupul "par" sau "impar" și adăugat la suma acelui grup cu sums[key] += n. O cheie nouă pornește de la 0, deci nu este nevoie de un caz special.
Ieșire: program Go și rezultatul capturat
package main
import "fmt"
func main() {
numbers := []int{3, 4, 5, 6, 7, 8}
// sums[k] pornește de la 0 pentru o cheie nouă, deci += funcționează imediat.
sums := make(map[string]int)
for _, n := range numbers {
key := "impar"
if n%2 == 0 {
key = "par"
}
sums[key] += n
}
fmt.Println(sums)
}
Construiește un string pentru fiecare grup
Primul număr al unui grup pornește string-ul. Fiecare număr următor este alipit cu "+". Indicatorul exists de la chains[key] distinge cele două cazuri. strconv.Itoa() transformă un număr în text.
Ieșire: program Go și rezultatul capturat
package main
import (
"fmt"
"strconv"
)
func main() {
numbers := []int{3, 4, 5, 6, 7, 8}
chains := make(map[string]string)
for _, n := range numbers {
key := "impar"
if n%2 == 0 {
key = "par"
}
// Primul element al unui grup pornește șirul; următoarele se adaugă cu "+".
current, exists := chains[key]
if !exists {
chains[key] = strconv.Itoa(n)
} else {
chains[key] = current + "+" + strconv.Itoa(n)
}
}
fmt.Println(chains)
}