プログラムを中心とした個人的なメモ用のブログです。 タイトルは迷走中。
内容の保証はできませんのであしからずご了承ください。

2019/05/09

[C#] Linq の GroupBy の使い方

event_note2019/05/09 6:54

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

IGroupingIEnumerable を実装しているので、グループの中身を列挙できます。

// 各グループのキーと個数を表示
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