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 — グループ化の基準になる関数。たとえば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Б」の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グループだけになる)するから注意!
GO TO FULL VERSION