При работе с последовательностями (списками) элементов бывает необходимым сгруппировать эти элементы по определенному признаку. Для этих целей в LINQ имеется метод расширения GroupBy.
Метод GroupBy
Допустим, у нас имеется набор данных по измерениям какой-то величины в различное время суток:
public class DataObject
{
public string Name { get; set; }
public double Value { get; set; }
public override string ToString()
{
return $"{Name} {Value}";
}
}
static void Main(string[] args)
{
List<DataObject> objects = new List<DataObject>()
{
new DataObject {Name = "Утро", Value = 12.5 },
new DataObject {Name = "День", Value = 12.1 },
new DataObject {Name = "Вечер", Value = 80.5 },
new DataObject {Name = "Ночь", Value = 11.3 },
new DataObject {Name = "Утро", Value = 10.7 },
new DataObject {Name = "День", Value = 12.1 },
new DataObject {Name = "Вечер", Value = 80.5 },
new DataObject {Name = "Вечер", Value = 11.3 }
};
}
Нам необходимо сгруппировать все элементы списка по времени суток. Используя метод расширения LINQ GroupBy, это можно сделать следующим образом:
var result = objects.GroupBy(o=>o.Name);
foreach (IGrouping<string, DataObject> datas in result)
{
Console.WriteLine($"Группа: {datas.Key}");
IEnumerable<DataObject> groupElements = datas.Take(datas.Count());
foreach (DataObject data in groupElements)
Console.WriteLine(data.ToString());
Console.WriteLine();
}
Результат выполнения этого кода будет следующим:
Утро 12,5
Утро 10,7
Группа: День
День 12,1
День 12,1
Группа: Вечер
Вечер 80,5
Вечер 80,5
Вечер 11,3
Группа: Ночь
Ночь 11,3
Метод GroupBy проводит группировку по заданному с помощью параметра критерию. В нашем случае — это была группировка по полю Name: var result = objects.GroupBy(o=>o.Name)
Результатом работы метода GroupBy является коллекция типа IEnumerable<IGrouping<string, object>> то есть — это список групп, полученных в результате выполнения метода GroupBy. string — определяет ключ для группы (в нашем случае — это значение свойства Name объекта), а object — тип сгруппированных данных (в нашем случае — это DataObject)
Таким образом, для того, чтобы получить сведения по каждой группе мы использовали два цикла foreach: первый цикл проходит по всем группам, а второй — по элементам внутри каждой группы.
Как и любые другие методы расширения LINQ, метод GroupBy можно использовать в связке с другими методами.
Совместное использование GroupBy и Select
Метод Select мы рассматривали, когда разбирались с тем, что такое проекции в LINQ и как они создаются. Рассмотрим следующий пример со списком, представленным выше:
var result = objects.GroupBy(o => o.Name).Select(n => new { Name = n.Key, Count = n.Count() });
foreach (var group in result)
{
Console.WriteLine($"{group.Name}: Количество элементов {group.Count}");
}
Здесь мы на основании группировки списка по полю Name создали новую проекцию — коллекцию объектов, каждый из которых содержит два поля: имя группы и количество элементов в списке. Результат выполнения этого кода будет следующим:
День: Количество элементов 2
Вечер: Количество элементов 3
Ночь: Количество элементов 1
Метод ToLookup
Метод ToLookup также может использоваться для группировки элементов последовательности по ключу, но при этом все элементы добавляются в словарь. Чтобы продемонстрировать различие между GrouBy и ToLookup воспользуемся этим методом и также сгруппируем все элементы списка, представленного выше по ключу Name.
ILookup<string, DataObject> res = objects.ToLookup(o => o.Name);
Console.WriteLine($"Количество групп {res.Count}");
foreach (IGrouping<string, DataObject> element in res)
{
Console.WriteLine($"Группа {element.Key}. Количество элементов: {element.Count()}");
foreach (DataObject data in element)
{
Console.WriteLine(data.ToString());
}
Console.WriteLine();
}
Результат выполнения будет следующим:
Группа Утро. Количество элементов: 2
Утро 12,5
Утро 10,7
Группа День. Количество элементов: 2
День 12,1
День 12,1
Группа Вечер. Количество элементов: 3
Вечер 80,5
Вечер 80,5
Вечер 11,3
Группа Ночь. Количество элементов: 1
Ночь 11,3
Объект, реализующий интерфейс ILookup, возвращаемый в результате выполнения метода ToLookup содержит свойство Count — количество групп, а также метод Contains, определяющий содержится ли группа с определенным названием (ключом) в полученной коллекции.
Итого
Сегодня мы рассмотрели способ группировки элементов последовательности в LINQ по определенному признаку. Результатом выполнения метода GroupBy в LINQ является коллекция групп. Используя метод Select также можно создавать свои проекции элементов, полученной последовательности.