JavaRush /Курси /C# SELF /Групування даних за допомогою

Групування даних за допомогою group by

C# SELF
Рівень 32 , Лекція 0
Відкрита

1. Вступ

Групування даних — це розділення елементів колекції на «кошики» залежно від спільної ознаки. Уявіть, що ви сортуєте яблука за кольором: усі червоні кладете в один кошик, зелені — в інший, жовті — в окремий. У програмуванні це називається групуванням.

Навіщо це потрібно?
У реальних завданнях часто корисно згрупувати людей за містом, продукти — за категорією, транзакції — за датою тощо. Це дає змогу робити корисну аналітику: наприклад, дізнатися, скільки студентів у кожному класі, або які міста мають найбільшу кількість студентів.

LINQ для цього надає спеціальний метод — GroupBy. Також є зручний аналог у синтаксисі запитів — group by. Розгляньмо цей процес на прикладах у нашому навчальному застосунку — системі обліку студентів і класів.

2. GroupBy у method syntax

Основна сигнатура й повернений результат

Метод GroupBy на перший погляд здається складним:


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

Де:

  • source — вихідна колекція (наприклад, список студентів).
  • keySelector — функція, за якою визначається ознака групування, наприклад, властивість ClassName або City.

Важливо!
Результат — не просто масив ваших об’єктів, а колекція спеціальних груп (IGrouping<TKey, TElement>). Кожна група містить:

  • Ключ групи (Key — те, що об’єднало елементи),
  • Колекцію елементів, що потрапили до цієї групи.

Схема:

Ключ (Key) Елементи групи (група об’єктів)
«9A» Іван, Олег, Марія
«10Б» Світлана, Петро

Приклад 1: Групуємо студентів за іменем класу

Припустімо, у нас є клас Student:


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

І колекція студентів:


var students = new List<Student>
{
    new Student { Name = "Іван", ClassName = "9A", Grade = 5 },
    new Student { Name = "Олег", ClassName = "9A", Grade = 4 },
    new Student { Name = "Марія", ClassName = "10Б", Grade = 5 },
    new Student { Name = "Світлана", ClassName = "10Б", Grade = 3 }
};

Групуємо студентів за класом:


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

foreach (var group in studentsByClass)
{
    Console.WriteLine($"Клас: {group.Key}"); // Ключ групи
    foreach (var student in group)
    {
        Console.WriteLine($" - {student.Name}, оцінка: {student.Grade}");
    }
}

Що відбувається?
Метод GroupBy створив дві групи: одну для «9A», іншу — для «10Б». Перебираючи групи, ми можемо виводити будь-яку агреговану інформацію про кожну групу.

Візуалізація: як виглядає колекція після GroupBy

Можна уявляти це так:


students.GroupBy(s => s.ClassName)
        ├─ group "9A" { Іван, Олег }
        └─ group "10Б" { Марія, Світлана }

Кожна «гілка» — окремий мінісписок студентів, об’єднаних за ключем.

Студенти за оцінкою: групування за довільними ознаками

Згрупуймо не за класами, а за оцінками!


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

foreach (var group in studentsByGrade)
{
    Console.WriteLine($"Оцінка: {group.Key}");
    foreach (var student in group)
    {
        Console.WriteLine($" - {student.Name} з класу {student.ClassName}");
    }
}

Виведення:


Оцінка: 5
 - Іван з класу 9A
 - Марія з класу 10Б
Оцінка: 4
 - Олег з класу 9A
Оцінка: 3
 - Світлана з класу 10Б

3. Обробка груп у LINQ: що можна робити після GroupBy

Дуже часто після групування ми хочемо не просто вивести групи, а отримати певну агреговану інформацію — наприклад, порахувати кількість елементів у кожній групі, обчислити середній бал або зібрати список імен.

Приклад 2: Підрахунок кількості студентів у кожному класі


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

foreach (var cc in classCounts)
{
    Console.WriteLine($"У класі {cc.Class} — {cc.Count} студентів");
}

Результат:


У класі 9A — 2 студентів
У класі 10Б — 2 студентів
  • Спочатку розбили студентів на групи.
  • Потім для кожної групи створили анонімний об’єкт із ключем (ім’я класу) і кількістю (метод Count()).

Приклад 3: зібрати список імен відмінників за класом


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($"У класі {group.Class} відмінники: {string.Join(", ", group.ExcellentStudents)}");
}

4. group by у query syntax

Якщо вам ближче щось схоже на SQL або ви працюєте з базами даних, LINQ підтримує синтаксис запитів виду group by ... into ....

Базовий приклад групування


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

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

Спочатку ми «групуємо» за ознакою, потім надаємо групі ім’я (into classGroup) — і далі можемо використовувати його як завгодно.

Проекція з агрегатами

Припустімо, хочемо дізнатися класи й кількість студентів у кожному класі:


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($"У класі {item.Class} — {item.Count} студентів");
}

— Синтаксис той самий, але після select можна робити будь-яку проекцію (агрегації, списки, середні значення тощо).

5. Додаткові сценарії

Групування за кількома ознаками (складений ключ)

Іноді потрібно групувати за комбінацією властивостей. Наприклад, за класом і оцінкою:


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

foreach (var group in byClassAndGrade)
{
    Console.WriteLine($"Клас: {group.Key.ClassName}, Оцінка: {group.Key.Grade}");
    foreach (var student in group)
    {
        Console.WriteLine($" - {student.Name}");
    }
}

Ключ групи тепер — анонімний тип (комбінація кількох полів).

Як працюють групи на практиці?

Трохи внутрішньої кухні LINQ

  • Коли ви викликаєте GroupBy, LINQ будує всередині спеціальну «таблицю»: для кожного унікального значення ключа — окрема група.
  • Групи реалізують інтерфейс IGrouping<TKey, TElement>. Можна звертатися до властивості Key, щоб отримати значення ознаки, за якою ви групували.

Відкладене виконання й продуктивність

  • Групи обчислюються не відразу, а лише коли ви починаєте їх перебирати (foreach). Тому для дуже великих колекцій не варто хвилюватися: групи фактично створюються тільки під час використання.
  • Якщо ви плануєте неодноразово звертатися до груп, викличте .ToList() або .ToArray(), щоб зафіксувати результат.

Практичне застосування

Групування дає змогу будувати складні звіти, статистику та аналітику. Ось кілька реальних прикладів, де це може стати в пригоді:

  • В інтернет-магазині: згрупувати замовлення за користувачем, щоб показати історію покупок кожного.
  • На освітній платформі: згрупувати студентів за викладачем або за предметом.
  • У HR-застосунку: згрупувати співробітників за відділом і порахувати чисельність персоналу.
  • На співбесіді можуть попросити написати групування реальних даних з агрегацією значень.

LINQ дозволяє дуже швидко прототипувати такі завдання, а вміння читати й писати групування — великий плюс для будь-якого .NET-розробника.

Візуальна схема

Ось проста блок-схема обробки колекції методом GroupBy:


[Колекція студентів] 
      |
[GroupBy за ClassName]
      |
[Група "9A"] ---> [Іван, Олег]
[Група "10Б"] --> [Марія, Світлана]

Кожна група — окрема мініколекція з ключем.

6. Часті помилки та підводні камені

Деякі студенти спочатку плутаються через специфіку результату GroupBy:

  • Після виклику GroupBy ви не отримаєте просто «групи» типу List<List<Student>>, а отримаєте колекцію IGrouping<TKey, TElement>. Для роботи з групами використовуйте властивість Key та ітерацію по елементах групи.
  • Групування за складним ключем (кілька полів): не забувайте використовувати анонімний тип або створювати окремий клас, якщо потрібен структурований ключ.
  • Якщо треба дізнатися лише кількість елементів у групах — не забувайте використовувати Select(g => g.Count()), інакше доведеться вручну рахувати елементи усередині кожної групи.
  • Інколи можна заплутатися з вкладеністю циклів: перший цикл — по групах, другий — по елементах усередині групи.
  • Якщо ви помилилися з лямбда-виразом або ключем, отримаєте не ті групи, на які очікували (або одну велику групу, якщо ключ у всіх однаковий).
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ