JavaRush /Курсы /C# SELF /Группировка данных с помощью

Группировка данных с помощью group by

C# SELF
32 уровень , 0 лекция
Открыта

1. Введение

Группировка данных — это разделение элементов коллекции на "корзины" в зависимости от общего признака. Представьте, что вы сортируете яблоки по цвету: все красные кладёте в одну корзинку, зелёные — в другую, жёлтые — ещё куда-нибудь. В программировании это называется группировкой.

Зачем она нужна?
В реальных задачах часто бывает полезно сгруппировать людей по городу, продукты по категории, транзакции по дате и так далее. Это позволяет делать полезную аналитику: например, узнать, сколько студентов в каждом классе, или какие города самые студенческие.

LINQ предоставляет для этого особый метод: GroupBy. Также есть удобный аналогичный оператор в query syntax — это 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 для получения значения признака, по которому вы группировали.

Lazy evaluation и производительность

  • Группы вычисляются не сразу, а только когда вы начинаете их перебирать (foreach). Поэтому, если очень большие коллекции — не бойтесь "создания" групп, пока к ним не обратились, они живут только в момент использования.
  • Если вы планируете неоднократно обращаться к группам — можно вызвать .ToList() или .ToArray(), чтобы "зафиксировать" результат.

Практическое применение

Группировка позволяет строить сложные отчёты, статистику и аналитику. Вот несколько реальных примеров, где она может пригодиться:

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

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

Визуальная схема

Вот простая блок-схема обработки коллекции методом GroupBy:


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

Каждая группа — отдельная мини-коллекция с ключом.

6. Частые ошибки и подводные камни

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

  • После вызова GroupBy вы не получите просто "группы" типа List<List<Student>>, а получите коллекцию IGrouping<TKey, TElement>. Для работы с группами используйте свойства Key и итерацию по элементам группы.
  • Группировка по сложному ключу (несколько полей): не забывайте использовать анонимный тип или создавать отдельный класс, если нужен структурированный ключ.
  • Если нужно узнать только количество элементов в группах — не забывайте использовать Select(g => g.Count()), иначе придётся вручную считать элементы внутри каждой группы.
  • Иногда можно запутаться с вложенностью циклов: первый цикл по группам, второй — по элементам внутри группы.
  • Если вы ошиблись с лямбдой или ключом, получите не те группы, которые ожидали (или одну большую группу, если ключ у всех одинаковый).
2
Задача
C# SELF, 32 уровень, 0 лекция
Недоступна
Ежемесячный отчёт по продажам
Ежемесячный отчёт по продажам
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ