言語
日本語
English

Caution

お使いのブラウザはJavaScriptが無効になっております。
当サイトでは検索などの処理にJavaScriptを使用しています。
より快適にご利用頂くため、JavaScriptを有効にしたうえで当サイトを閲覧することをお勧めいたします。

C#辞典

  1. トップページ
  2. C#辞典
  3. Enumerable.GroupBy()

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()』が役立ちます。

記事の間違いや著作権の侵害等ございましたらお手数ですがまでご連絡頂ければ幸いです。