【C#】LINQのAggregateメソッドで畳み込み演算と複雑な集計を行う

LINQにはSum(合計)やMax(最大値)といった便利な集計メソッドが用意されていますが、これらはすべて特定の計算に特化したものです。「文字列を特定の区切り文字で連結したい」「条件に応じて累積値を変化させたい」といった、より汎用的な集計処理を行いたい場合、LINQの最も強力かつ柔軟なメソッドであるAggregateを使用します。

他のプログラミング言語では「Reduce(畳み込み)」と呼ばれることが多いこのメソッドについて、ログデータの集約と最大値検索のロジックを例に解説します。


目次

Aggregateメソッドの概要

Aggregateは、シーケンスの要素を一つずつ取り出し、**累積値(アキュムレータ)**に対して処理を適用していくことで、最終的に一つの値を導き出すメソッドです。

基本的な処理の流れは以下の通りです。

  1. 最初の要素(または初期値)を「現在の累積値」とする。
  2. 次の要素を取り出し、「現在の累積値」と「次の要素」を使って計算を行う。
  3. 計算結果を新しい「累積値」とする。
  4. これを最後の要素まで繰り返す。

実践的なコード例:ログメッセージの結合と最長データの特定

以下のコードは、システムが出力した複数の短いログメッセージを1行の文字列に結合する処理と、リストの中で最も長い(文字数が多い)メッセージを特定する処理の2つを実装した例です。

using System;
using System.Collections.Generic;
using System.Linq;

namespace LogAnalysisSystem
{
    class Program
    {
        static void Main()
        {
            // シナリオ:
            // サーバーから出力されたエラーコードやメッセージのリストがある。
            // 1. これらをパイプライン記号 "|" で連結して1行のログファイル用テキストにしたい。
            // 2. 最も文字数が長い(詳細な)エラーメッセージを特定したい。

            var errorMessages = new[]
            {
                "Timeout",
                "ConnectionLost",
                "NullReferenceException",
                "IOError",
                "UnauthorizedAccess"
            };

            // --- 例1: 文字列の連結(CSVやログ行の生成) ---
            
            // Aggregateの引数: (現在の累積値, 次の要素) => 新しい累積値
            // 処理の流れ:
            // 1. "Timeout" (初期値)
            // 2. "Timeout" + "|" + "ConnectionLost"
            // 3. "Timeout|ConnectionLost" + "|" + "NullReferenceException" ...
            string combinedLog = errorMessages.Aggregate((current, next) => $"{current}|{next}");

            Console.WriteLine("--- 連結されたログ ---");
            Console.WriteLine(combinedLog);


            // --- 例2: 初期値(シード)を指定した最長文字列の検索 ---

            // 比較の基準となる初期値として空文字 "" を設定します。
            // 第1引数: 初期値 (seed)
            // 第2引数: (最長候補, 次の要素) => 判定ロジック
            string longestMessage = errorMessages.Aggregate(
                "", 
                (longest, next) => next.Length > longest.Length ? next : longest
            );

            Console.WriteLine("\n--- 最も長いエラーメッセージ ---");
            Console.WriteLine($"Message: {longestMessage}");
            Console.WriteLine($"Length : {longestMessage.Length}");
        }
    }
}

実行結果

--- 連結されたログ ---
Timeout|ConnectionLost|NullReferenceException|IOError|UnauthorizedAccess

--- 最も長いエラーメッセージ ---
Message: NullReferenceException
Length : 22

技術的なポイントとオーバーロード

1. アキュムレータ(累積値)の理解

Aggregateを使いこなす鍵は、ラムダ式の第1引数が「これまでの計算結果(累積値)」であり、第2引数が「これから処理する要素」であることを理解することです。

  • Func<TSource, TSource, TSource>
    • Aggregate((acc, next) => acc + next)
    • リストの最初の要素が自動的に初期値として使用されます。要素数が0の場合は例外が発生するため注意が必要です。

2. シード値(初期値)の指定

Aggregateには初期値(シード)を指定するオーバーロードが存在します。

  • Aggregate<TSource, TAccumulate>(TAccumulate seed, Func<TAccumulate, TSource, TAccumulate>)

例2のように「最長文字列を探す」場合、リストが空であってもエラーにならず、初期値(この場合は空文字)が返されるため安全です。また、数値の合計を計算する際に「基本料金1000円からスタートして加算する」といった場合にも有用です。

3. 計算結果の変換(第3引数)

さらに、集計が終わった後に結果を別の型に変換するための第3引数を取るオーバーロードもあります。例えば、文字列を連結した後に、最終的にその文字数を返すといった処理が可能です。

// 集計後に最終結果を加工する例(大文字に変換して返す)
var result = errorMessages.Aggregate(
    "",                            // 初期値
    (acc, next) => acc + next,     // 集計処理(単純連結)
    final => final.ToUpper()       // 結果変換処理
);

まとめ

Aggregateメソッドは、LINQにおける最も基本的かつ汎用的な集計メソッドです。SumJoinでは対応できない複雑な計算ロジック(前の結果を利用して次の値を決定する処理など)が必要な場合、Aggregateを使用することで、foreach文による一時変数の管理を排除し、宣言的なコードを記述することが可能になります。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次