Enumerable.GroupBy()
| 対応: | C# 3.0(2007) |
|---|
シーケンスの要素をキーごとにグループ化する LINQ の拡張メソッドです。キー引き で高速アクセスできる『ToLookup()』も合わせて解説します。
構文
using System.Linq; // キーセレクターでグループ化します。 IEnumerable<IGrouping<TKey, T>> groups = source.GroupBy(x => キー); // キーと値を別々に指定してグループ化します。 IEnumerable<IGrouping<TKey, TVal>> groups2 = source.GroupBy(x => キー, x => 値); // 辞書のようにキーで直接アクセスできる Lookup を作成します。 ILookup<TKey, T> lookup = source.ToLookup(x => キー);
メソッド一覧
| メソッド | 概要 |
|---|---|
| GroupBy(keySelector) | キーセレクターでグループ化した『IGrouping』の列挙を返します。 |
| GroupBy(keySelector, elementSelector) | グループ内の要素も変換してグループ化します。 |
| GroupBy(keySelector, resultSelector) | 各グループをまとめて変換した結果を返します。 |
| ToLookup(keySelector) | 即時評価でキーと要素のコレクションをマッピングした『Lookup』を返します。 |
サンプルコード
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
var products = new List<(string Name, string Category, int Price)>
{
("りんご", "果物", 150),
("みかん", "果物", 80),
("にんじん", "野菜", 100),
("バナナ", "果物", 120),
("キャベツ", "野菜", 200),
};
// カテゴリーでグループ化します。
var groups = products.GroupBy(p => p.Category);
foreach (var group in groups)
{
Console.WriteLine($"【{group.Key}】");
foreach (var item in group)
Console.WriteLine($" {item.Name}: {item.Price}円");
}
// グループ内の要素数と合計を集計します。
var summary = products.GroupBy(p => p.Category)
.Select(g => new
{
Category = g.Key,
Count = g.Count(),
Total = g.Sum(x => x.Price),
});
foreach (var s in summary)
Console.WriteLine($"{s.Category}: {s.Count}件, 合計 {s.Total}円");
// ToLookup でキーを指定して素早くアクセスします(即時評価)。
ILookup<string, string> lookup = products.ToLookup(p => p.Category, p => p.Name);
foreach (var name in lookup["果物"])
Console.WriteLine(name);
コンパイルして実行すると次のようになります。
dotnet run 【果物】 りんご: 150円 みかん: 80円 バナナ: 120円 【野菜】 にんじん: 100円 キャベツ: 200円 果物: 3件, 合計 350円 野菜: 2件, 合計 300円 りんご みかん バナナ
よくあるミス
よくあるミス: GroupBy() の結果を 2 回列挙すると 2 回クエリが走る
GroupBy() は遅延評価のため、列挙するたびに元シーケンスを再走査します。同じグループ結果を複数回参照する場合は、ToList() で実体化してから使ってください。即時評価が必要な場合は ToLookup() も有効です。
using System;
using System.Collections.Generic;
using System.Linq;
var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
// NG: groups を 2 回列挙すると、元シーケンスを 2 回走査する
var groups = numbers.GroupBy(n => n % 2 == 0 ? "偶数" : "奇数");
Console.WriteLine(groups.Count()); // 走査 1 回目
foreach (var g in groups) // 走査 2 回目
Console.WriteLine($"{g.Key}: {g.Count()}件");
修正後は次の通りです。
using System;
using System.Collections.Generic;
using System.Linq;
var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
// OK: ToList() で実体化してから複数回参照する
var groups = numbers.GroupBy(n => n % 2 == 0 ? "偶数" : "奇数").ToList();
Console.WriteLine(groups.Count);
foreach (var g in groups)
Console.WriteLine($"{g.Key}: {g.Count()}件");
修正後は次の通りです。
dotnet run 2 奇数: 3件 偶数: 3件 2 奇数: 3件 偶数: 3件
概要
『GroupBy()』は遅延評価で動作し、グループを表す『IGrouping<TKey, TElement>』の列挙を返します。各グループは『Key』プロパティでグループキーを取得でき、そのまま列挙すると要素を取り出せます。
『ToLookup()』は即時評価のため、作成後はキーを指定して複数回参照するのに向いています。『ToLookup()』は存在しないキーにアクセスしても例外を投げず、空のシーケンスを返します。一方、同様に見える『Dictionary』はキーが存在しない場合に例外を投げます。
グループ化した後に並べ替えを行う場合は『Enumerable.OrderBy()』を参照してください。グループごとの集計には『Enumerable.Count() / Sum() / Average()』が役立ちます。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。