Linq の GroupBy の使い方についてまとめてみます。
概要
GroupBy
でグループ化のキーを指定します。GroupBy
の戻り値は IGrouping
で、以下のように定義されています。
IGrouping<キーの型, グループ化されたオブジェクトの型>
public interface IGrouping<out TKey, out TElement> : IEnumerable<TElement>, IEnumerable
{
TKey Key { get; }
}
基本
プリミティブ型の配列やリスト
// Japan と America が重複しているリスト
// (わかりやすいように重複する要素は連続して入れています)
var countries = new List<string>() {
"Japan", "Japan", "Japan", "America", "America", "China" };
// string のリストなので、グループ化のキーは自身となる
var groupedCountries = countries.GroupBy(x => x);
// 各グループのキーと個数を表示
foreach (var groupedCountry in groupedCountries)
{
Console.WriteLine($"{groupedCountry.Key}, {groupedCountry.Count()}");
}
出力結果
Japan, 3
America, 2
China, 1
IGrouping
は IEnumerable
を実装しているので、グループの中身を列挙できます。
// 各グループのキーと個数を表示
foreach (var groupedCountry in groupedCountries)
{
Console.WriteLine($"Key:{groupedCountry.Key}, Count:{groupedCountry.Count()}");
// 各グループの中身を表示
foreach (var country in groupedCountry)
{
Console.WriteLine(country);
}
}
出力結果
Key:Japan, Count:3
Japan
Japan
Japan
Key:America, Count:2
America
America
Key:China, Count:1
China
オブジェクトの配列やリスト
以下のようなクラスをモデルとします。
class Person
{
public string Name { get; set; }
public string Age { get; set; }
}
以下のようなデータ格納されているとします。
var persons = new List<Person>
{
new Person(){ Name = "Hoge", Age = 20 },
new Person(){ Name = "Piyo", Age = 20 },
new Person(){ Name = "Foo", Age = 30 },
new Person(){ Name = "Bar", Age = 30 },
};
Age
でグループ化してみます。
// Age でグループ化
var groupedPersons = persons.GroupBy(x => x.Age);
foreach (var groupedPerson in groupedPersons)
{
Console.WriteLine($"Key:{groupedPerson.Key}, Count:{groupedPerson.Count()}");
// 各グループの中身を表示
foreach (var person in groupedPerson)
{
Console.WriteLine($"Name:{person.Name}, Age:{person.Age}");
}
}
出力結果
Key:20, Count:2
Name:Hoge, Age:20
Name:Piyo, Age:20
Key:30, Count:2
Name:Foo, Age:30
Name:Bar, Age:30
応用
重複チェック
GroupBy
を使うことで、配列やリストに重複しているものがあるかどうかをチェックできます。
前述の以下のリストについて。
var countries = new List<string>() {
"Japan", "Japan", "Japan", "America", "America", "China" };
単純に重複しているかどうかをチェックしたい場合は以下のように書けます。
// 重複チェック
var result = countries.GroupBy(x => x).Any(x => x.Count() > 1);
Console.WriteLine(result);
拡張メソッドを作成しておけば楽かもしれません。
public static bool IsDuplicated<T, U>(this IEnumerable<T> enumerable, Func<T, U> keySelector)
{
return enumerable.GroupBy(keySelector).Any(x => x.Count() > 1);
}
public static bool IsDuplicated<T>(this IEnumerable<T> enumerable)
{
return enumerable.GroupBy(x => x).Any(x => x.Count() > 1);
}
Console.WriteLine(countries.IsDuplicated());
重複している(していない)要素を取得
重複している要素を取得したい場合は以下のように書けます。
// 重複しているデータを取得
var keys = countries.GroupBy(x => x).Where(x => x.Count() > 1).Select(x => x.Key);
Console.WriteLine("重複しているデータ: " + string.Join(", ", keys));
// 重複していないデータを取得
keys = countries.GroupBy(x => x).Where(x => x.Count() == 1).Select(x => x.Key);
Console.WriteLine("重複していないデータ: " + string.Join(", ", keys));
出力結果
重複しているデータ: Japan, America
重複していないデータ: China