いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Linq入門記-40 (LINQ To Object, Aggregate)


今回は、Aggregate拡張メソッドについてです。


Aggregate拡張メソッドは、各集計用拡張メソッド(Sum, Min, Maxなど)の親みたいな感じです。
この拡張メソッドは、特定の目的の為に存在する集計関数ではなく、独自の集計処理を作成する際に利用します。


Pythonのmap関数みたいな感じです。


これを利用すると、独自の集計処理を行う事ができます。
つまり、SumやMinやMax拡張メソッドなどは、Aggregate拡張メソッドの特殊なパターンといえますね。
どれもAggregate拡張メソッドで同じ事が行えます。


Aggregate拡張メソッドを利用するのは、集計処理を行う必要があるけれども
Sumなどの標準で用意されている集計関数では、処理出来ない場合に利用します。
(多いのが、集計対象が独自オブジェクトのシーケンスで、集計結果も独自オブジェクトで返す必要があったりする場合とかですね)


書式は以下の通り。

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

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

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


一つ目の書式は、seedを指定しないパターンです。
この場合、ソースシーケンスの最初の要素がseedとなります。


2つめの書式が、個人的に一番よく利用します。
seedを指定して、繰り返し実行される関数を指定します。
指定する関数には、第一引数としてアキュムレータ、つまり、現在の集計値が渡され、
第二引数には、ソースシーケンスの現在の要素が渡されます。
この関数の中で、アキュムレータに値を設定するか、新たに作成します。
それが、ソースシーケンスをループしている間、ずっと渡され続けます。


3つ目は、2つ目の動作を行った後、最後にresultSelectorにて、結果のデータを決定します。


以下、サンプルです。

プログラミング MICROSOFT LINQ (マイクロソフト公式解説書 Microsoft Visual Studi)

プログラミング MICROSOFT LINQ (マイクロソフト公式解説書 Microsoft Visual Studi)

のAggregateの所のサンプルを参考にしてます。ってかほとんどそのままですw m(_ _)m

    #region LinqSamples-37
    public class LinqSamples37 : IExecutable
    {
        class Order
        {
            public string Name   { get; set; }
            public int    Amount { get; set; }
            public int    Month  { get; set; }
        }
        
        public void Execute()
        {
            //
            // Aggregate拡張メソッド.
            //
            // Aggregate拡張メソッドは、指定されたseedを起点値としてfunc関数を繰り返し呼び出し
            // 結果をアキュムレーターに保持してくれるメソッド。
            //
            // pythonのmap関数みたいなものである。
            //
            // Aggregateは、独自の集計処理を行う場合に利用する。
            // 尚、Sum, Min, Max, Average拡張メソッドなどはAggregateの特殊なパターンといえる。
            // (つまり、全部Aggregateで同じように処理できる。)
            //
            // 利用する際の注意点としては、seedを明示的に指定しない場合、暗黙的に
            // ソースシーケンスの最初の要素をseedとして利用して処理してくれることである。
            // 通常の場合はこれで良いが、出来るだけseedは明示的に渡した方がよい。
            //
            
            //
            // Sum拡張メソッドの動きをAggregateで行う.
            //
            var query   = Enumerable.Range(1, 10).Aggregate
                          (
                            0,              // seed
                            (a, s) => a + s // func
                          );
            
            Console.WriteLine("========= Sum拡張メソッドの動作 ==========");
            Console.WriteLine("SUM = [{0}]", query);
            Console.WriteLine("======================================");
            
            //
            // 独自の集計処理を行ってみる.
            //   以下は各オーダーの発注最高額とその月を求める。
            //
            Console.WriteLine("========= 独自の集計処理実行 ==========");
            
            //
            // ソースシーケンス.
            //
            var orders = new Order[]
                         {
                             new Order { Name = "gsf_zero1", Amount = 1000, Month = 3 },
                             new Order { Name = "gsf_zero1", Amount = 600, Month = 4 },
                             new Order { Name = "gsf_zero1", Amount = 100, Month = 5 },
                             new Order { Name = "gsf_zero2", Amount = 100, Month = 3 },
                             new Order { Name = "gsf_zero2", Amount = 1000, Month = 4 },
                             new Order { Name = "gsf_zero2", Amount = 1200, Month = 5 },
                             new Order { Name = "gsf_zero3", Amount = 1000, Month = 3 },
                             new Order { Name = "gsf_zero3", Amount = 1200, Month = 4 },
                             new Order { Name = "gsf_zero3", Amount = 900, Month = 5 },
                         };
            
            //
            // 集計する前に、オーダー名単位でグループ化.
            //
            var orderGroupingQuery = from  theOrder in orders
                                     group theOrder by theOrder.Name;
            
            //
            // 最高発注額を求める。
            //
            var maxOrderQuery = from orderGroup in orderGroupingQuery
                                select
                                    new
                                    {
                                        Name     = orderGroup.Key,
                                        MaxOrder = orderGroup.Aggregate
                                                   (
                                                        new { MaxAmount = 0, Month = 0 },  // seed
                                                        (a, s) => (a.MaxAmount > s.Amount) // func
                                                                    ? a 
                                                                    : new { MaxAmount = s.Amount, Month = s.Month }
                                                   )
                                    };
            
            foreach (var item in maxOrderQuery)
            {
                Console.WriteLine(item);
            }
            Console.WriteLine("======================================");
        }
    }
    #endregion


実行結果は以下の通りです。

  ========= Sum拡張メソッドの動作 ==========
  SUM = [55]
  ======================================
  ========= 独自の集計処理実行 ==========
  { Name = gsf_zero1, MaxOrder = { MaxAmount = 1000, Month = 3 } }
  { Name = gsf_zero2, MaxOrder = { MaxAmount = 1200, Month = 5 } }
  { Name = gsf_zero3, MaxOrder = { MaxAmount = 1200, Month = 4 } }
  ======================================

================================
過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。