C# の LINQ でグループ化する GroupBy
GroupBy はシーケンスの要素をキーでグループ化するメソッドだ。SQL の GROUP BY と同様の機能を提供する。
基本的な使い方
同じキーを持つ要素をまとめる。
var people = new List<Person>
{
new Person { Name = "Alice", City = "Tokyo" },
new Person { Name = "Bob", City = "Osaka" },
new Person { Name = "Charlie", City = "Tokyo" },
new Person { Name = "Diana", City = "Osaka" },
new Person { Name = "Eve", City = "Tokyo" }
};
var byCity = people.GroupBy(p => p.City);
foreach (var group in byCity)
{
Console.WriteLine($"--- {group.Key} ---");
foreach (var person in group)
{
Console.WriteLine($" {person.Name}");
}
}出力結果は以下のようになる。
--- Tokyo ---
Alice
Charlie
Eve
--- Osaka ---
Bob
DianaGroupBy の戻り値は IEnumerable<IGrouping<TKey, TElement>> だ。各グループは Key プロパティでグループのキー値にアクセスでき、グループ自体が IEnumerable<TElement> として要素を列挙できる。
グループの集計
グループ化した後に集計を行うのは非常によくあるパターンだ。
var orders = new List<Order>
{
new Order { Product = "Apple", Quantity = 5 },
new Order { Product = "Banana", Quantity = 3 },
new Order { Product = "Apple", Quantity = 2 },
new Order { Product = "Cherry", Quantity = 10 },
new Order { Product = "Banana", Quantity = 7 }
};
var summary = orders
.GroupBy(o => o.Product)
.Select(g => new
{
Product = g.Key,
TotalQuantity = g.Sum(o => o.Quantity),
OrderCount = g.Count()
});
foreach (var item in summary)
{
Console.WriteLine($"{item.Product}: {item.TotalQuantity}個 ({item.OrderCount}件)");
}
// Apple: 7個 (2件)
// Banana: 10個 (2件)
// Cherry: 10個 (1件)商品ごとにグループ化し、各グループの合計数量と注文件数を計算している。
複数キーでのグループ化
匿名型を使えば、複数のプロパティをキーにできる。
var sales = new List<Sale>
{
new Sale { Year = 2023, Month = 1, Amount = 100 },
new Sale { Year = 2023, Month = 1, Amount = 200 },
new Sale { Year = 2023, Month = 2, Amount = 150 },
new Sale { Year = 2024, Month = 1, Amount = 300 }
};
var byYearMonth = sales
.GroupBy(s => new { s.Year, s.Month })
.Select(g => new
{
g.Key.Year,
g.Key.Month,
Total = g.Sum(s => s.Amount)
});
foreach (var item in byYearMonth)
{
Console.WriteLine($"{item.Year}/{item.Month}: {item.Total}");
}
// 2023/1: 300
// 2023/2: 150
// 2024/1: 300年と月の組み合わせでグループ化している。匿名型のプロパティは Key.Year、Key.Month でアクセスできる。
要素の変換
グループ化と同時に、要素を変換することもできる。
var people = new List<Person>
{
new Person { Name = "Alice", City = "Tokyo", Age = 25 },
new Person { Name = "Bob", City = "Osaka", Age = 30 },
new Person { Name = "Charlie", City = "Tokyo", Age = 35 }
};
// 都市でグループ化し、名前だけを保持
var byCity = people.GroupBy(
p => p.City,
p => p.Name
);
foreach (var group in byCity)
{
Console.WriteLine($"{group.Key}: {string.Join(", ", group)}");
}
// Tokyo: Alice, Charlie
// Osaka: Bob第二引数のラムダ式で、グループに含める要素の形を指定できる。この例では Person オブジェクト全体ではなく、名前だけをグループに含めている。
クエリ構文での表現
クエリ構文では group ... by を使う。
var people = new List<Person>
{
new Person { Name = "Alice", City = "Tokyo" },
new Person { Name = "Bob", City = "Osaka" },
new Person { Name = "Charlie", City = "Tokyo" }
};
var byCity = from p in people
group p by p.City;
foreach (var group in byCity)
{
Console.WriteLine($"{group.Key}: {group.Count()}人");
}
// Tokyo: 2人
// Osaka: 1人into を使えば、グループ化した結果をさらに操作できる。
var byCity = from p in people
group p by p.City into cityGroup
where cityGroup.Count() >= 2
select new { City = cityGroup.Key, Count = cityGroup.Count() };ToDictionary との違い
GroupBy と似た機能に ToDictionary があるが、用途が異なる。
GroupBy
同じキーを持つ複数の要素をグループ化。遅延評価。
ToDictionary
各要素をキーと値のペアに変換。キーの重複は例外。即時評価。
キーが重複する可能性がある場合は GroupBy、一意のキーでマッピングする場合は ToDictionary を使う。