CodeGym /Corsi /C# SELF /Raggruppamento dei dati con ...

Raggruppamento dei dati con group by

C# SELF
Livello 32 , Lezione 0
Disponibile

1. Introduzione

Il raggruppamento dei dati è il processo di dividere gli elementi di una collezione in "cestini" in base a una caratteristica comune. Immagina di ordinare le mele per colore: tutte le rosse in un cestino, le verdi in un altro, le gialle da un’altra parte. In programmazione questo si chiama raggruppamento.

Perché serve?
Nella vita reale spesso è utile raggruppare persone per città, prodotti per categoria, transazioni per data e così via. Questo permette di fare analisi utili: ad esempio, sapere quanti studenti ci sono in ogni classe, oppure quali città sono le più “studentesche”.

LINQ ti dà un metodo speciale per questo: GroupBy. C’è anche un operatore simile nella query syntax — group by. Vediamo tutto con esempi sulla nostra app didattica — un sistema per gestire studenti e classi.

2. GroupBy in method syntax

Firma base e risultato restituito

Il metodo GroupBy sembra un po’ spaventoso:


IEnumerable<IGrouping<TKey, TElement>> GroupBy<TElement, TKey>(
    this IEnumerable<TElement> source,
    Func<TElement, TKey> keySelector
)

Dove:

  • source — la collezione di partenza (tipo una lista di studenti).
  • keySelector — la funzione che decide su cosa raggruppare, tipo la proprietà ClassName o City.

Occhio!
Il risultato non è solo un array dei tuoi oggetti, ma una collezione di gruppi speciali (IGrouping<TKey, TElement>). Ogni gruppo contiene:

  • La chiave del gruppo (Key — quello che ha unito gli elementi),
  • La collezione degli elementi che sono finiti in quel gruppo.

Schema:

Chiave (Key) Elementi del gruppo (gruppo di oggetti)
"9A" Ivan, Oleg, Maria
"10B" Svetlana, Piotr
... ...

Esempio 1: Raggruppiamo gli studenti per nome della classe

Supponiamo di avere una classe Student:


public class Student
{
    public string Name { get; set; }
    public string ClassName { get; set; }
    public int Grade { get; set; }
}

E una collezione di studenti:


var students = new List<Student>
{
    new Student { Name = "Ivan", ClassName = "9A", Grade = 5 },
    new Student { Name = "Oleg", ClassName = "9A", Grade = 4 },
    new Student { Name = "Maria", ClassName = "10B", Grade = 5 },
    new Student { Name = "Svetlana", ClassName = "10B", Grade = 3 }
};

Raggruppiamo gli studenti per classe:


var studentsByClass = students.GroupBy(s => s.ClassName);

foreach (var group in studentsByClass)
{
    Console.WriteLine($"Classe: {group.Key}"); // Chiave del gruppo
    foreach (var student in group)
    {
        Console.WriteLine($" - {student.Name}, voto: {student.Grade}");
    }
}

Cosa succede?
Il metodo GroupBy ci ha creato due gruppi: uno per "9A", l’altro per "10B". Scorrendo i gruppi, puoi stampare qualsiasi info aggregata su ogni “gruppetto”.

Visualizzazione: com’è la collezione dopo GroupBy

Pensala così:


students.GroupBy(s => s.ClassName)
        ├─ gruppo "9A" { Ivan, Oleg }
        └─ gruppo "10B" { Maria, Svetlana }

Ogni “ramo” è una mini-lista di studenti uniti dalla stessa chiave.

Studenti per voto: raggruppamento per qualsiasi caratteristica

Raggruppiamo non per classe, ma per voto!


var studentsByGrade = students.GroupBy(s => s.Grade);

foreach (var group in studentsByGrade)
{
    Console.WriteLine($"Voto: {group.Key}");
    foreach (var student in group)
    {
        Console.WriteLine($" - {student.Name} dalla classe {student.ClassName}");
    }
}

Output:


Voto: 5
 - Ivan dalla classe 9A
 - Maria dalla classe 10B
Voto: 4
 - Oleg dalla classe 9A
Voto: 3
 - Svetlana dalla classe 10B

3. Lavorare con i gruppi in LINQ: cosa puoi fare dopo GroupBy

Spesso dopo il raggruppamento non vuoi solo stampare i gruppi, ma ottenere qualche info aggregata — tipo contare quanti elementi ci sono in ogni gruppo, calcolare la media dei voti, raccogliere la lista dei nomi.

Esempio 2: Contare quanti studenti ci sono in ogni classe


var classCounts = students
    .GroupBy(s => s.ClassName)
    .Select(g => new { Class = g.Key, Count = g.Count() });

foreach (var cc in classCounts)
{
    Console.WriteLine($"Nella classe {cc.Class} — {cc.Count} studenti");
}

Risultato:


Nella classe 9A — 2 studenti
Nella classe 10B — 2 studenti
  • Prima abbiamo diviso gli studenti in gruppi.
  • Poi per ogni gruppo abbiamo creato un oggetto anonimo con la chiave (nome della classe) e il conteggio (metodo Count()).

Esempio 3: Raccogliere la lista dei nomi degli studenti eccellenti per classe


var excellentByClass = students
    .Where(s => s.Grade == 5)
    .GroupBy(s => s.ClassName)
    .Select(g => new 
    {
        Class = g.Key,
        ExcellentStudents = g.Select(s => s.Name).ToList()
    });

foreach (var group in excellentByClass)
{
    Console.WriteLine($"Nella classe {group.Class} eccellenti: {string.Join(", ", group.ExcellentStudents)}");
}

4. group by in query syntax

Se ti piace qualcosa che assomiglia a SQL, o vieni dal mondo dei database, LINQ supporta la sintassi delle query tipo group by ... into ....

Esempio base di raggruppamento


var studentsByClass =
    from s in students
    group s by s.ClassName into classGroup
    select classGroup;

foreach (var group in studentsByClass)
{
    Console.WriteLine($"Classe: {group.Key}");
    foreach (var student in group)
    {
        Console.WriteLine($" - {student.Name}");
    }
}

Prima “raggruppiamo” per una caratteristica, poi diamo un nome al gruppo (into classGroup) — e poi puoi usarlo come vuoi.

Proiezione con aggregati

Supponiamo di voler sapere le città e quanti studenti ci sono in ogni città:


var countsByCity =
    from s in students
    group s by s.ClassName into classGroup
    select new { Class = classGroup.Key, Count = classGroup.Count() };

foreach (var item in countsByCity)
{
    Console.WriteLine($"Nella classe {item.Class} — {item.Count} studenti");
}

— La sintassi è la stessa, ma dopo select puoi fare qualsiasi proiezione (aggregati, liste, medie ecc).

5. Scenari extra

Raggruppamento per più caratteristiche (chiave composta)

A volte serve raggruppare per una combinazione di proprietà. Tipo per classe e voto:


var byClassAndGrade = students
    .GroupBy(s => new { s.ClassName, s.Grade });

foreach (var group in byClassAndGrade)
{
    Console.WriteLine($"Classe: {group.Key.ClassName}, Voto: {group.Key.Grade}");
    foreach (var student in group)
    {
        Console.WriteLine($" - {student.Name}");
    }
}

La chiave del gruppo ora è un tipo anonimo (combinazione di più campi).

Come funzionano i gruppi nella pratica?

Un po’ di “cucina interna” di LINQ

  • Quando fai GroupBy, LINQ costruisce internamente una specie di “tabella”: per ogni valore unico della chiave — un gruppo separato.
  • I gruppi implementano l’interfaccia IGrouping<TKey, TElement>. Puoi accedere alla proprietà Key per ottenere il valore della caratteristica su cui hai raggruppato.

Lazy evaluation e performance

  • I gruppi non vengono calcolati subito, ma solo quando inizi a scorrerli (foreach). Quindi, anche se hai collezioni enormi — non preoccuparti, i gruppi “vivono” solo quando li usi davvero.
  • Se pensi di accedere più volte ai gruppi — puoi chiamare .ToList() o .ToArray() per “fissare” il risultato.

Applicazioni pratiche

Il raggruppamento ti permette di costruire report complessi, statistiche e analisi. Ecco qualche esempio reale dove può tornare utile:

  • In un e-commerce: raggruppare gli ordini per utente, per mostrare la cronologia acquisti di ciascuno.
  • Su una piattaforma educativa: raggruppare gli studenti per insegnante o per materia.
  • In un’app HR: raggruppare i dipendenti per dipartimento e contare il personale.
  • Al colloquio: potrebbero chiederti di scrivere un raggruppamento di dati reali con aggregazione dei valori.

LINQ ti permette di prototipare queste cose al volo, e saper leggere/scrivere raggruppamenti è un super plus per ogni dev .NET.

Schema visivo

Ecco un semplice diagramma a blocchi di come funziona GroupBy su una collezione:


[Collezione di studenti] 
      |
[GroupBy per ClassName]
      |
[Gruppo "9A"] ---> [Ivan, Oleg]
[Gruppo "10B"] --> [Maria, Svetlana]

Ogni gruppo è una mini-collezione con la sua chiave.

6. Errori comuni e trappole

Alcuni studenti all’inizio si confondono per via dello specifico risultato di GroupBy:

  • Dopo aver chiamato GroupBy non ottieni semplicemente “gruppi” tipo List<List<Student>>, ma una collezione di IGrouping<TKey, TElement>. Per lavorare con i gruppi usa la proprietà Key e itera sugli elementi del gruppo.
  • Raggruppamento per chiave complessa (più campi): ricordati di usare un tipo anonimo o creare una classe separata se vuoi una chiave strutturata.
  • Se ti serve solo sapere quanti elementi ci sono nei gruppi — non dimenticare di usare Select(g => g.Count()), altrimenti dovrai contare a mano gli elementi dentro ogni gruppo.
  • A volte ci si può confondere con i cicli annidati: il primo ciclo sui gruppi, il secondo sugli elementi dentro il gruppo.
  • Se sbagli la lambda o la chiave, ottieni gruppi diversi da quelli che ti aspettavi (o un unico grande gruppo se la chiave è uguale per tutti).
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION