1. 需要
アキュムレータとは何か?アキュムレータの使い方は?焦らずに、新しい技術が生まれるのは基本的に何らかのニーズを満たすためであり、ニーズから考えることで、この関数の特性を理解しやすくなります。
理解を容易にするために、int型の1次元配列があり、5つの数字が格納されていると仮定し、最初の値(-1)と2番目の値(0)の和(-1)を求め、その求めた値(-1)と配列の3番目の数(3)を加算し、再度和(2)を求め、さらにその値と配列の4番目の数(5)を加算する... これを繰り返し、結局は int result = -1 + 0 + 3 + 5 + 8; となる。
int[] numbers={-1, 0, 3, 5,8};
1.1 基本要件
//V1.0 バージョン
static void Main(string[] args)
{
int[] numbers = { -1, 0, 3, 5, 8 };
int result = numbers[0];
for (int i = 1; i < numbers.Length; i++)
{
result = result + numbers[i];
}
Console.WriteLine(result);//出力:15
Console.ReadKey();
}
1.2 アルゴリズム部分のカプセル化
中央のアルゴリズムを抽出することで、他の異なる長さのint型配列を入力できるようになります:
//V1.1 バージョン
static void Main(string[] args)
{
int[] numbers = { -1, 0, 3, 5, 8 };
var result = Aggregate(numbers);
Console.WriteLine(result);
Console.ReadKey();
}
static int Aggregate(int[] array)
{
int result = array[0];
for (int i = 1; i < array.Length; i++)
{
result = result + array[i];
}
return result;
}
1.3 加算だけではない – アルゴリズムの置き換え
アキュムレータは加算だけでなく、例えば次のような演算も行える必要があります:
int result = -1 * 0 * 3 * 5 * 8;
int result = -1 - 0 - 3 - 5 - 8;
//V1.2 バージョン、アルゴリズム置き換えの実現
// デリゲート、ラムダに関する知識が必要
static void Main(string[] args)
{
int[] numbers = { -1, 0, 3, 5, 8 };
var result = Aggregate(numbers, (result, next) => result * next); // 乗算を実現
//var result = Aggregate(numbers, (result, next) => result - next); // 減算を実現
Console.WriteLine(result);
Console.ReadKey();
}
static int Aggregate(int[] array, Func<int, int, int> func)
{
int result = array[0];
for (int i = 1; i < array.Length; i++)
{
result = func(result, array[i]);
}
return result;
}
1.4 int型だけではない – ジェネリック
現在このアルゴリズム int Aggregate(int[] array, Func<int,int,int> func) はint型のみサポートしていますが、任意の型に拡張する必要があります – ジェネリック。
//V1.3 バージョン、ジェネリックの実現
// ジェネリック、IEnumerator インターフェースに関する知識が必要
static void Main(string[] args)
{
int[] numbers = { -1, 0, 3, 5, 8 };
var result = Aggregate(numbers, (result, next) => result + next);
Console.WriteLine(result);
Console.ReadKey();
}
static TSource Aggregate<TSource>(IEnumerable<TSource> source, Func<TSource, TSource, TSource> func)
{
using (IEnumerator<TSource> e = source.GetEnumerator())
{
if (!e.MoveNext())
{
throw new ArgumentException();
}
TSource result = e.Current;
while (e.MoveNext())
{
result = func(result, e.Current);
}
return result;
}
}
1.5 拡張メソッドとしての実装
IEnumerator インターフェースを実装したクラスに直接拡張メソッドを追加します。これで、この Aggregate の実装は公式で提供されているアキュムレータと類似したものになります。
//V1.4 バージョン、拡張メソッド
class Program
{
static void Main(string[] args)
{
int[] numbers = { -1, 0, 3, 5, 8 };
var result = numbers.Aggregate((result, next) => result + next);
Console.WriteLine(result);
Console.ReadKey();
}
}
public static class Helper
{
public static TSource Aggregate<TSource>(this IEnumerable<TSource> source, Func<TSource, TSource, TSource> func)
{
using (IEnumerator<TSource> e = source.GetEnumerator())
{
if (!e.MoveNext())
{
throw new ArgumentException("少なくとも2つの要素が必要です");
}
TSource result = e.Current;
while (e.MoveNext())
{
result = func(result, e.Current);
}
return result;
}
}
}
2. マイクロソフト提供のAPI
System.Linq 名前空間には、次のものが用意されています:
1. public static TSource Aggregate<TSource>(this IEnumerable<TSource> source, Func<TSource, TSource, TSource> func);
2. public static TAccumulate Aggregate<TSource, TAccumulate>(this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> func);
3. public static TResult Aggregate<TSource, TAccumulate, TResult>(this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> func, Func<TAccumulate, TResult> resultSelector);
このうち最初のインターフェースは、上記の例と同じ機能です。他の2つの機能については後で説明します(時間があれば)。
3. 浅いところから深いところへ
初心者の場合、頭の中ではこの Aggregate は足し算・引き算・掛け算・割り算しかできないと思いがちですが、もしそう考えるなら、まだ理解が浅いということです。ここでこのメソッドの引数 func を深く分析してみましょう。
下図に示すように、アキュムレータは各要素を走査します。最初は最初と2番目の要素を直接 func 演算する以外は、3番目以降は前回の計算結果 result を func の最初の入力パラメーターとし、2番目の入力パラメーターは配列の次の要素とします。最後まで走査したら、最終的な result の値を返します。
実際の使用例:データベース内の1つのテーブルが複数のテーブルの外部キーを参照している場合、これらの外部キーをクエリする必要があるとき、アキュムレータを使用して、クエリ条件を累積し、データベースにクエリを実行できます。
