for WPF developers
Home Profile Tips 全記事一覧

Aggregate 拡張メソッドで独自の集計をおこなう

(2017/03/07 16:33:36 created.)

(2017/03/13 7:55:23 modified.)

Aggregate 拡張メソッドは指定した関数によってコレクションの各要素を計算し、あるひとつの値を算出するために使用します。

例えば下記のようなコレクションを考えます。


このコレクションに対する Aggregate 拡張メソッドによる操作は次のようなイメージになります。


図中の Func はアキュームレータ関数と呼ばれ、Aggregate 拡張メソッドで入力引数として指定する関数です。Aggregate 拡張メソッドでは、現在の値 current と次の値 next を使ってなんらかの処理をおこなった結果を繰り返し同じ処理をおこないます。例えば足し算をおこなうように Func を指定すると、各要素すべてを足し合わせることになるため、コレクションの合計値が算出できます。また、掛け算をおこなうように Func を指定すると、各要素すべてを掛け合わせた値が算出できます。

実際のコードでこの動作を確認してみましょう。

Program.cs
  1. namespace Tips_Linq
  2. {
  3.     using System;
  4.     using System.Collections.Generic;
  5.     using System.Linq;
  6.  
  7.     class Program
  8.     {
  9.         static void Main(string[] args)
  10.         {
  11.             var numbers = new int[] { 1, 2, 3, 4 };
  12.             Console.WriteLine("コレクションの要素は {{ {0} }} です。", string.Join(", ", numbers));
  13.  
  14.             var sum = numbers.Aggregate((current, next) => current + next);
  15.             var mul = numbers.Aggregate((current, next) => current * next);
  16.             Console.WriteLine("合計値は {0}、全部掛けると {1} になりました。", sum, mul);
  17.  
  18.             Console.ReadKey();
  19.         }
  20.     }
  21. }


これを利用して二乗和を計算してみましょう。実はちょっと注意しなければいけません。とりあえずコードを見てみましょう。

Program.cs
  1. namespace Tips_Linq
  2. {
  3.     using System;
  4.     using System.Linq;
  5.  
  6.     class Program
  7.     {
  8.         static void Main(string[] args)
  9.         {
  10.             var numbers = new int[] { 2, 3, 4, 5 };
  11.             Console.WriteLine("コレクションの要素は {{ {0} }} です。", string.Join(", ", numbers));
  12.  
  13.             var powSum = numbers.Aggregate((current, next) => current + next * next);
  14.             Console.WriteLine("二乗和は {0} です?", powSum);
  15.  
  16.             powSum = numbers.Aggregate(0, (current, next) => current + next * next);
  17.             Console.WriteLine("二乗和は {0} です。", powSum);
  18.  
  19.             Console.ReadKey();
  20.         }
  21.     }
  22. }


13 行目で二乗和を計算させたつもりですが、Aggregate 拡張メソッドは先ほどの図のように、始めの計算では current にコレクションの先頭要素、next に次の要素が割り当てられるため、上記のように next を二乗させていると、先頭要素だけ二乗されないままになってしまいます。

Aggregate 拡張メソッドに初期値を指定することで、次の図のような計算過程となります。先ほどの図とは初めの計算が異なり、current に初期値、next にコレクションの先頭要素が割り当てられるようになり、その分 Func による計算回数が 1 回増えています。

コードでは 16 行目のように第 1 引数に初期値を指定できるオーバーロードが用意されています。


二乗和を求める場合は初期値を与えることで実現できました。それでは、二乗和の平均を求める場合はどうすればいいでしょうか。二乗和をデータ数で割ればいいのでやり方はいくらでもあるのですが、Aggregate 拡張メソッドで完結させることができます。

Program.cs
  1. namespace Tips_Linq
  2. {
  3.     using System;
  4.     using System.Linq;
  5.  
  6.     class Program
  7.     {
  8.         static void Main(string[] args)
  9.         {
  10.             var numbers = new int[] { 2, 3, 4, 5 };
  11.             Console.WriteLine("コレクションの要素は {{ {0} }} です。", string.Join(", ", numbers));
  12.  
  13.             var powSum = numbers.Aggregate(0, (current, next) => current + next * next);
  14.             Console.WriteLine("二乗和は {0} です。", powSum);
  15.  
  16.             var powSumAve = numbers.Aggregate(0, (current, next) => current + next * next, x => (double)x / numbers.Length);
  17.             Console.WriteLine("二乗和の平均は {0} です。", powSumAve);
  18.  
  19.             Console.ReadKey();
  20.         }
  21.     }
  22. }


13 行目は二乗和を求めるために使用した Aggregate 拡張メソッドで、初期値を含んでいます。これにさらに変換関数を追加したオーバーロードを使用したコードが 16 行目になります。変換関数は第 3 引数に指定します。第 1 引数 と第 2 引数は 13 行目とまったく同じで、第 3 引数に計算結果をデータ数で割る処理を指定しています。

最後に Aggregate 拡張メソッドのオーバーロードを確認しましょう。

関数のオーバーロード 説明
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); シーケンスにアキュムレータ関数を適用します。指定したシード値は最初のアキュムレータ値として使用され、指定した関数は結果値の選択に使用されます。

これまでの例で上記 3 つのオーバーロードを使用しましたが、実はちょっとずつ指定できる関数や戻り値に違いがあります。初期値も何も指定しない場合、指定できるアキュームレータ関数の引数と戻り値は元のシーケンスの型と同一である必要があります。その他のオーバーロードでは、アキュームレータ関数の引数に元のシーケンスの型とは異なる型を指定することができます。したがって、初期値を指定しなくても実現できる処理であったとしても、その戻り値の型を変更したい場合にあえて初期値を与えるケースもあり得ます。