CodeGym /コース /C# SELF /group byでデータをグループ化する

group byでデータをグループ化する

C# SELF
レベル 32 , レッスン 0
使用可能

1. はじめに

データのグループ化っていうのは、コレクションの要素を共通の特徴で「カゴ分け」することだよ。たとえばリンゴを色ごとに分けるみたいな感じ。赤いのは一つのカゴ、緑は別のカゴ、黄色もまた別のカゴ。プログラミングではこれをグループ化って呼ぶんだ。

なんで必要なの?
実際の課題では、人を都市ごとに、商品をカテゴリごとに、取引を日付ごとにグループ化したいことがよくある。これで便利な分析ができるんだ。たとえば、各クラスに何人学生がいるかとか、どの都市が一番学生が多いかとかね。

LINQにはこのための特別なメソッドGroupByがあるし、クエリ構文にも同じようなgroup byオペレーターがあるよ。じゃあ、学生とクラスの管理システムを例にして、実際にやってみよう!

2. method syntaxでのGroupBy

基本のシグネチャと返り値

GroupByメソッドの見た目はちょっと怖いかも:


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

どんな意味かというと:

  • source — 元のコレクション(たとえば学生リストとか)。
  • keySelector — グループ化の基準になる関数。たとえばClassNameCityプロパティ。

重要!
返り値はただのオブジェクト配列じゃなくて、特別なグループのコレクション(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Б」の2つのグループができたよ。グループごとに好きな集計情報を出せるんだ。

ビジュアライズ: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

SQLっぽい書き方が好きな人や、DBの世界から来た人には、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で回し始めたときに初めて作られる。だから、めっちゃ大きいコレクションでも「グループ作成」を怖がらなくてOK。使うときだけ生きてる感じ。
  • 何度もグループにアクセスしたい場合は、.ToList().ToArray()で「結果を確定」させておくといいよ。

実践での使い道

グループ化は複雑なレポートや統計、分析を作るのに超便利。実際に役立つ例をいくつか挙げると:

  • ネットショップ:ユーザーごとに注文をグループ化して購入履歴を表示。
  • 教育プラットフォーム:先生ごとや科目ごとに学生をグループ化。
  • HRアプリ:部署ごとに社員をグループ化してheadcountを数える。
  • 面接で:実データのグループ化+集計を書くように言われることもあるよ。

LINQならこういう課題もサクッとプロトタイピングできるし、グループ化を読んだり書いたりできるのは.NETエンジニアとして大きな強みだよ。

図解スキーム

これがGroupByメソッドでコレクションを処理する簡単なブロック図:


[学生コレクション] 
      |
[ClassNameでGroupBy]
      |
[グループ "9A"] ---> [イワン、オレグ]
[グループ "10Б"] --> [マリヤ、スヴェトラナ]

各グループはキー付きのミニコレクションだよ。

6. よくあるミスや落とし穴

最初はGroupByの返り値のクセで混乱する学生も多いんだ:

  • GroupByを呼んだ後、ただのList<List<Student>>みたいな「グループ」じゃなくて、IGrouping<TKey, TElement>コレクションになる。グループを扱うにはKeyプロパティやグループ内の要素のループを使おう。
  • 複雑なキー(複数フィールド)でグループ化する場合は、匿名型か専用クラスを使うのを忘れずに。構造化されたキーが必要なときは特にね。
  • グループ内の要素数だけ知りたいなら、Select(g => g.Count())を使うのを忘れずに。そうしないと、毎回手動で数える羽目になるよ。
  • ループのネストで混乱しがち:最初のループはグループごと、次のループはグループ内の要素ごと。
  • ラムダやキーの指定を間違えると、期待したグループにならなかったり(全員同じキーなら1グループだけになる)するから注意!
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION