Iterable.groupBy în Kotlin

Împarte elementele în grupe după o cheie și întoarce un map: cheie → lista elementelor din acea grupă.

Sintaxă
fun <T, K> Iterable<T>.groupBy(cheie: (T) -> K): Map<K, List<T>>
fun <T, K, V> Iterable<T>.groupBy(cheie: (T) -> K, valoare: (T) -> V): Map<K, List<V>>

Ce face Iterable.groupBy

groupBy { ... } calculează pentru fiecare element o cheie (orașul, prima literă, nota) și pune elementul în lista cheii respective. Rezultatul este un Map<K, List<T>>. Cheile apar în ordinea în care au fost întâlnite prima dată, iar în fiecare listă elementele își păstrează ordinea.

A doua formă primește și o funcție pentru valori: în listă nu se pune elementul întreg, ci doar ce întoarce această funcție (de exemplu doar numele elevului).

Dacă vrei doar câte elemente are fiecare grupă, groupingBy { ... }.eachCount() face asta fără să creeze listele.

Parametri

Parametru Descriere
cheie Funcția care dă grupa fiecărui element.
valoare Opțional: ce se pune în listă pentru fiecare element (implicit elementul întreg).

Ce returnează

Un Map<K, List<T>> (sau Map<K, List<V>>) cu ordinea cheilor păstrată.

Exemple

Exemplul 1: Elevi grupați pe orașe

Cheia este orașul. Parcurgem map-ul și afișăm fiecare oraș cu elevii lui.

data class Elev(val nume: String, val oras: String)

fun main() {
    val elevi = listOf(
        Elev("Ana", "Iași"), Elev("Ion", "Cluj"), Elev("Maria", "Iași"),
        Elev("Radu", "Brașov"), Elev("Dan", "Cluj"),
    )
    val peOrase = elevi.groupBy { it.oras }
    for ((oras, grup) in peOrase) {
        println("$oras: ${grup.map { it.nume }}")
    }
}

Rezultat

Iași: [Ana, Maria]
Cluj: [Ion, Dan]
Brașov: [Radu]

Exemplul 2: Cu funcție pentru valori

Al doilea argument spune ce păstrăm în listă: doar numele. Așa nu mai e nevoie de map la afișare.

data class Elev(val nume: String, val medie: Double)

fun main() {
    val elevi = listOf(Elev("Ana", 9.5), Elev("Ion", 6.8), Elev("Maria", 8.2), Elev("Radu", 4.5))
    val categorii = elevi.groupBy(
        { if (it.medie >= 9) "excelent" else if (it.medie >= 5) "promovat" else "corigent" },
        { it.nume },
    )
    println(categorii)
}

Rezultat

{excelent=[Ana], promovat=[Ion, Maria], corigent=[Radu]}

Exemplul 3: Cuvinte după prima literă

Cheia este un caracter. Cheile apar în ordinea primei întâlniri, nu alfabetic; pentru ordine alfabetică folosim toSortedMap().

fun main() {
    val cuvinte = listOf("mere", "banane", "măsline", "pere", "brânză", "morcovi")
    val dupaLitera = cuvinte.groupBy { it.first() }
    println(dupaLitera)
    println(dupaLitera.toSortedMap())
}

Rezultat

{m=[mere, măsline, morcovi], b=[banane, brânză], p=[pere]}
{b=[banane, brânză], m=[mere, măsline, morcovi], p=[pere]}

Greșeli frecvente

  • Te aștepți ca cheile să fie sortate: sunt în ordinea primei apariții. Sortează cu toSortedMap() dacă ai nevoie.
  • Folosești groupBy { ... }.mapValues { it.value.size } doar pentru numărare: groupingBy { ... }.eachCount() este mai direct.

Comenzi înrudite

  • Iterable.groupingBy: Pregătește o grupare după cheie; eachCount() numără apoi câte elemente are fiecare cheie (frecvența).
  • Iterable.partition: Împarte colecția în două liste: elementele care îndeplinesc condiția și cele care nu o îndeplinesc.
  • Iterable.associateBy: associateBy face un map cu elementele ca valori și o cheie calculată; associateWith ține elementele ca chei.
  • Map.mapValues: Creează un map nou cu aceleași chei și valori transformate (mapValues) sau cu aceleași valori și chei transformate (mapKeys).
  • Iterable.distinct: Elimină duplicatele și întoarce o listă cu fiecare element o singură dată, în ordinea primei apariții.