CodeGym /コース /C# SELF /ちょっと進んだ集約: Aggregateの使い方

ちょっと進んだ集約: Aggregateの使い方

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

1. はじめに

昔ながらのLINQメソッドって、学食のご飯みたいなもんだよね:"合計"とか"最小"とか"平均"を選んで、はい次!みたいな。でも、たまには特別なものが欲しくなる。そんな時にAggregateが登場。まるでシェフが君のレシピで料理してくれる感じ。

関数型プログラミングで言うと、AggregateはReduce(またはfold)だね。コレクションを一歩ずつたたんで一つの値にしていく。どうやるかは君次第。完全にコントロールできて、クリエイティブになれるよ。

Aggregateで便利に解けるタスク例:

  • 複雑な合計の計算:例えば全ての数の積、偶数/奇数だけの合計、特別なルールでの合計とか。
  • 手動ロジックで文字列を連結(例えば偶数・奇数インデックスで区切り文字を変えるとか)。
  • 文字列レポートの生成("Markdownリスト"、HTML、カスタムフォーマットなど)。
  • 状態を変えながらコレクションを作る(例えば、特別なキーでオブジェクトリストから辞書を作るとか)。
  • 標準の集約関数にない複雑なカウント。

2. Aggregateメソッドのシグネチャと動作原理

Microsoftの公式ドキュメントでEnumerable.Aggregateを見てみよう:

public static TAccumulate Aggregate<TSource, TAccumulate>(
    this IEnumerable<TSource> source, 
    TAccumulate seed, 
    Func<TAccumulate, TSource, TAccumulate> func
)

それぞれの意味はこんな感じ:

  • source — 君の元のコレクション。
  • seed — "初期値"(スタートのアキュムレータだと思えばOK)。
  • func — 2つの引数を取る関数:蓄積値(acc)、コレクションの現在要素。新しい蓄積値を返す。

もっとシンプルなオーバーロードもあるよ:

public static TSource Aggregate<TSource>(
    this IEnumerable<TSource> source, 
    Func<TSource, TSource, TSource> func
)

この場合、"初期値"はコレクションの最初の要素で、funcは2番目から最後まで呼ばれる。

3. Aggregateの超シンプルな使い方例

学生向けのちょっとしたクイズ:AggregateでSumやProductの代わりになるって知ってた?

int[] numbers = { 2, 3, 4 };

// 合計を計算
int sum = numbers.Aggregate((acc, val) => acc + val); // acc = 蓄積, val = 次の要素

// 積を計算
int product = numbers.Aggregate((acc, val) => acc * val);

Console.WriteLine(sum);     // 9
Console.WriteLine(product); // 24

見た目はSumやMultiplyっぽいけど、自分でやってる!冗談だけど、Sumメソッドなら合計だけ、でもAggregateなら合計も"アンチ合計"も"平方根の合計"もできるよ。

4. 文字列連結にAggregateを使う

全部の文字列をカンマでつなげて一つにしよう(最後に余計なカンマは付けない):

string[] words = { "C#", "LINQ", "rocks" };
string result = words.Aggregate((acc, word) => acc + ", " + word);
// 結果: "C#, LINQ, rocks"

コレクションが空かもしれない時は、初期値(seed)が便利:

// 空文字から始める
string report = words.Aggregate(
    "テクノロジー: ",
    (acc, word) => acc + word + "; ",
    acc => acc.TrimEnd(' ', ';') // 最後の";"を消す
);

Console.WriteLine(report); // "テクノロジー: C#; LINQ; rocks"

3つ目の引数(結果セレクタ)に注目。seed付きオーバーロードにあるやつで、まるでデザートで仕上げる感じ。

5. Aggregateをアプリで使う

前からいじってる学習アプリを思い出してみて。例えばStudentクラスがあるとする:

public class Student
{
    public string Name { get; set; }
    public int Grade { get; set; }
}

学生リスト:

var students = new List<Student>
{
    new Student { Name = "アリサ", Grade = 5 },
    new Student { Name = "ボブ", Grade = 4 },
    new Student { Name = "バシャ", Grade = 3 },
    new Student { Name = "マリア", Grade = 5 }
};

タスク:こんな文字列を作る
"ベスト: アリサ, マリア"
— つまりGrade == 5の人全員。

初心者がよくやるやり方:

var best = "";
foreach (var s in students)
{
    if (s.Grade == 5)
        best += s.Name + ", ";
}
best = best.TrimEnd(',', ' ');
Console.WriteLine("ベスト: " + best);

じゃあLINQスタイルでAggregateを使うと:

var bestStr = students
    .Where(s => s.Grade == 5)
    .Select(s => s.Name)
    .Aggregate("ベスト: ", (acc, name) => acc + name + ", ")
    .TrimEnd(',', ' ');

Console.WriteLine(bestStr);

いい感じ:コード最小、読みやすさ最大。上司がLINQ知らなくても意図は伝わる(たぶん)。

6. もっと複雑なシナリオ

実はAggregateのアキュムレータは数値や文字列だけじゃなく、辞書でも自作クラスでも何でもOK。

例えば:学生リストから各成績ごとの人数を数える場合:

var gradeCounts = students.Aggregate(
    new Dictionary<int, int>(),
    (dict, student) => {
        if (dict.ContainsKey(student.Grade))
            dict[student.Grade]++;
        else
            dict[student.Grade] = 1;
        return dict;
    }
);

// 出力用:
foreach (var pair in gradeCounts)
{
    Console.WriteLine($"成績 {pair.Key}: {pair.Value} 人の学生");
}

このやり方はGroupByを手動でやってる感じ。なんでGroupByを使わないの?って思うかもだけど、たまに標準LINQメソッドにない特別な集約が必要な時があるんだ。例えばバシャ以外の学生だけ合計したいとか、レポート用のレポートを作りたい時とか。

7. Aggregateの動き方を図解(フローチャート)

配列{ 2, 4, 3 }で合計をアキュムレートする場合:


acc: 2(最初の要素)
      |
      v
val: 4
acc = acc + val = 2 + 4 = 6
      |
      v
val: 3
acc = acc + val = 6 + 3 = 9
      |
      v
[全要素処理済み]
      |
      v
結果: 9

seed付きオーバーロードの場合も同じ流れ:


seed: 0
      |
      v
val: 2
acc = 0 + 2 = 2
      |
      v
val: 4
acc = 2 + 4 = 6
      |
      v
val: 3
acc = 6 + 3 = 9
      |
      v
結果: 9

Aggregateと他の集約メソッドの比較

メソッド 標準の動作 柔軟性 空コレクションの場合 動作例
Sum
数値を合計 低い 0を返す
[1,2,3].Sum() = 6
Count
要素数を数える 低い 0を返す
[a,b].Count() = 2
Aggregate
どんな集約もOK 高い seedが必要
[1,2,3].Aggregate((a,b)=>a*b)=6
string.Join
文字列を連結 中くらい 空なら""
string.Join(", ", arr)

8. 実践アドバイス・よくあるミス・大事なポイント

Aggregateは柔軟すぎて、使い方を間違えると予想外のことが起きる。よくあるミスは:

たまにプログラマーがseed(初期値)を忘れて、コレクションが空の時にseed無しオーバーロードを使うとInvalidOperationExceptionが投げられる。だから空コレクションにはseed付きオーバーロードを使おう:

var sum = new int[0].Aggregate(0, (acc, n) => acc + n); // OK! 0を返す

文字列をAggregateで連結すると、最後に余計な区切り文字(例えば",")が付きやすい。.TrimEnd(',', ' ')で消すか、単に文字列連結だけならstring.Joinを使おう。

可変型アキュムレータ(ListやDictionaryなど)をAggregateで使う場合、in-placeで変更すると全ステップで同じオブジェクト参照になる。並列処理やコピーを期待してると変なことになるので、純粋関数型っぽく毎回新しいオブジェクトを返すのがベター。

他の人が使うコードでは、"カッコよさ"のためだけにAggregateを使いすぎないで。初心者にはforeachや標準集約の方が分かりやすい。単純なタスクならSum、Count、Joinを使おう。でも"特別なテンプレでテキストを作る"ならAggregateが最強!

9. 実務・面接・フレームワークとの関係

業界では、Aggregateは変わった集計やデータのたたみ込みが必要な時によく使われる。例えば複雑なレポートや統計、グラフ、ハッシュ計算、ユニークid生成、コレクションからUIコンポーネントを作る時とか。

面接ではLINQの質問がよく出るよ:"配列の合計をどうやって取る?"、"文字列リストを一つの文字列にするには?"、"ユニーク要素の数をどう数える?"みたいな。LINQやAggregateを使った答えが期待されることも多い。ちょっとひねった質問もある:"LINQで偶数の平方の合計を計算して、その過程を説明する文字列を返せますか?"とかね。

Entity Framework、Dapper、RavenDBみたいな有名.NETライブラリやフレームワークでは、AggregateはDB側で直接使うことは少ないけど、コード側でのメモリ集約にはよく使われるよ。

2
タスク
C# SELF, レベル 32, レッスン 4
ロック未解除
一番シンプルな集約 — 配列の積
一番シンプルな集約 — 配列の積
1
アンケート/クイズ
データのグループ化、レベル 32、レッスン 4
使用不可
データのグループ化
上級LINQ:グループ化と集約関数
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION