Aggregate?
Aggregateメソッドとはなんぞや
Microsoft曰く
まずはMSの公式ドキュメントを見てみます。
シーケンスにアキュムレータ関数を適用します。
(中略)
一般的な集計操作を簡略化するために、標準クエリ演算子には、汎用カウントメソッド、 Count 、および4つの数値集計メソッド (、、、および) も含まれて Min Max Sum Average います。
🤔???????
MSの公式ドキュメントは翻訳の質が低いことが多くて困りますね。
概要
"Aggregate"は日本語で「集計」とか大体その辺を意味する言葉です。
その名の通り、IEnumerable<T>を継承したコレクションの中身全てについて集計処理を行ってくれる汎用性の高いメソッドです。
やろうと思えばLinqの他のメソッド、Min・MaxもSumもAverageも、Aggregateを使えば自力で書けたりするわけですね。
(処理の意味の明確化のためにはそれ用にあつらえてあるメソッドを使うべきですが)
Aggregateの動き方
使い方を知る前に、Aggregateがどのような動きをするのか確認しておきましょう。
なお、サンプルコードはトップレベルステートメントを使っているので、C# 9(.NET 5) 以降のバージョンでしかコンパイルが通らない点に注意してください。
Aggregateメソッドには3つのオーバーロードがあります。
まずは一番基本的な、集計用のラムダ式一つだけを引数としてとるものからはじめます。
using System;
using System.Linq;
var array = new[] { 1, 2, 3, 4, 5 };
var sum = array.Aggregate((result, current) => result + current);
Console.WriteLine($"1~5 の総和は{sum}です!"); // 1~5 の総和は15です!
Aggregateに渡す集計用ラムダ式には2つの引数が必要です。
上記の例ではresultとcurrentと名付けています。
それではAggregateメソッドを実行したときの動きを追ってみましょう。
初回はresultに最初の要素が、currentには次の要素が代入される。
この例だとresult = 1 current = 2
result + currentが評価され、結果がreturnされる。
例では3が返ります。
返り値がresultに新たに代入され、currentに次の要素が入る。
result = 3 current = 3
同様に最後の要素まで計算する。
つまり、Aggregateにわたすラムダ式の構成要素は以下のようなものと言えるわけです:
第一引数には直前の評価の戻り値が入っていて、次の評価に値を伝えてくれる。
第ニ引数には現在の要素が次々と入っていく。
ラムダ式の戻り値が次に持ち越される計算結果になる。
このことがわかれば、あとの2つのオーバーロードの理解も簡単です。単に処理の前後に何かしらの処理をくっつけているだけなので。
using System;
using System.Linq;
var array = new[] { 1, 2, 3, 4, 5 };
// 第一引数として値や変数を渡すことで、第二引数のラムダ式の最初の引数 result の初期値を決められます。
// 初期値を与えたとき、最初の current はコレクションの最初の要素になります。
var sum1 = array.Aggregate(15, (result, current) => result + current);
Console.WriteLine($"15から始めた総和は{sum1}です!"); // 15から始めた総和は30です!
// 第三引数としてラムダ式を渡すことで、計算結果に対して何かしらの処理を施すことができます。
var sum2 = array.Aggregate(0, (result, current) => result + current, result => result * result);
Console.WriteLine($"1~5 の総和の2乗は{sum2}です!"); // 1~5 の総和の2乗は225です!
便利な使い方
Aggregateの動き方がわかったところで、ようやく本題の具体的にはどんな活用方法があるかを紹介していきます。
文字列の連結
要素を文字列化して任意のセパレータを挟んだ文字列に変換できます。
using System;
using System.Linq;
var array = new[] { 1, 2, 3, 4, 5 };
// 要素の加工はSelectによって予め済ましておくほうが、Aggregateに渡すラムダ式を簡潔にできます。
var csvRow = array.Select(i => i.ToString()).Aggregate((result, current) => $"{result}, {current}");
Console.WriteLine(csvRow);
// 出力
// 1, 2, 3, 4, 5
var matrix = Enumerable.Repeat(csvRow, 4);
var csvMat = matrix.Aggregate((result, current) => result + Environment.NewLine + current);
Console.WriteLine(csvMat);
// 出力
// 1, 2, 3, 4, 5
// 1, 2, 3, 4, 5
// 1, 2, 3, 4, 5
// 1, 2, 3, 4, 5
複数要素の同時集計
Linqに用意されているSumやAverage、Min・Maxメソッドは同時に一つの値についてしか計算できません。
リストの中に入っているオブジェクトの複数のプロパティについてこれらの結果を求めたい場合、Aggregateを使って自分で書いてやる必要があります。
using System;
using System.Linq;
using System.Drawing;
var array = new[] {
new Point(1, 4),
new Point(3, 2),
new Point(2, 8),
new Point(4, 6)
};
var sum = array.Aggregate((result, current) => new Point(result.X + current.X, result.Y + current.Y));
Console.WriteLine(sum); // {X=10,Y=20}
var max = array.Aggregate((result, current) =>
new Point(
Math.Max(result.X, current.X),
Math.Max(result.Y, current.Y)
));
Console.WriteLine(max); // {X=4,Y=8}
複数の条件を満たしているかの調査
条件をまとめた配列があれば、真理値の総計を取ることでそれらを満たしているかの調査が1行で書けてしまいます。
using System;
using System.Linq;
var conditions = new Func<int, bool>[] {
(value) => 0 < value,
(value) => value < 10,
(value) => value % 2 == 0
};
// すべての条件を満たしているか?
// 論理積計算のときは初期値を true にします。
var x = 3;
var isSatisfy = conditions.Aggregate(true, (result, current) => result && current(x));
Console.WriteLine(isSatisfy); // False
var y = 4;
isSatisfy = conditions.Aggregate(true, (result, current) => result && current(y));
Console.WriteLine(isSatisfy); // True
// 少なくとも一つの条件を満たしているか?
// 論理和計算のときは初期値を false にします。
var z = -1;
isSatisfy = conditions.Aggregate(false, (result, current) => result || current(z));
Console.WriteLine(isSatisfy); // True
おわり
紹介する使い方は以上でおわりですが、Aggregateメソッドは汎用性の高いメソッドなので、この他にもアイデア次第でいろんな事ができるでしょう。
ただ、慣れていないと中々何をやっているかがわかりにくくなりやすいメソッドでもあると思います。
実際に活用する際はAggregateをそのまま使用するのではなく、拡張メソッドにラップして、どのような処理なのかを明確にすることも検討したほうがいいかもしれませんね。
↧