LINQ Aggregateアルゴリズムの説明


722

これは不自然に聞こえるかもしれませんが、私は本当に良い説明を見つけることができませんでしたAggregate

良いとは、短く、わかりやすく、包括的で、小さくて明確な例を意味します。

回答:


1015

の最も理解しやすい定義Aggregateは、前に行った操作を考慮して、リストの各要素に対して操作を実行することです。つまり、最初の要素と2番目の要素に対してアクションを実行し、結果を転送します。次に、前の結果と3番目の要素に対して演算を実行します。等

例1.数値を合計する

var nums = new[]{1,2,3,4};
var sum = nums.Aggregate( (a,b) => a + b);
Console.WriteLine(sum); // output: 10 (1+2+3+4)

これを追加12て作り3ます。次に、3(前の結果)と3(シーケンスの次の要素)を追加してmakeを作成し6ます。次に、64を追加します10

例2.文字列の配列からcsvを作成する

var chars = new []{"a","b","c", "d"};
var csv = chars.Aggregate( (a,b) => a + ',' + b);
Console.WriteLine(csv); // Output a,b,c,d

これはほとんど同じ方法で機能します。aカンマを連結しbて作るa,b。次にa,b 、コンマと連結しcてmakeしa,b,cます。等々。

例3.シードを使用して数値を乗算する

完全を期すために、シード値をとるオーバーロードAggregateあります。

var multipliers = new []{10,20,30,40};
var multiplied = multipliers.Aggregate(5, (a,b) => a * b);
Console.WriteLine(multiplied); //Output 1200000 ((((5*10)*20)*30)*40)

上記の例と同様に、これはの値で始まり5、シーケンスの最初の要素を乗算して10、結果をにし50ます。この結果は繰り越され、シーケンスの次の数値で乗算されて20の結果が得られ1000ます。これは、シーケンスの残りの2つの要素まで続きます。

ライブの例:http : //rextester.com/ZXZ64749
ドキュメント:http : //msdn.microsoft.com/en-us/library/bb548651.aspx


補遺

上記の例2では、​​文字列連結を使用して、コンマで区切られた値のリストを作成しています。これは、Aggregateこの回答の意図であった使用法を説明する単純な方法です。ただし、この手法を使用して実際に大量のコンマ区切りデータを作成する場合はStringBuilder、を使用する方が適切でありAggregate、シードされたオーバーロードを使用してを開始することと完全に互換性がありStringBuilderます。

var chars = new []{"a","b","c", "d"};
var csv = chars.Aggregate(new StringBuilder(), (a,b) => {
    if(a.Length>0)
        a.Append(",");
    a.Append(b);
    return a;
});
Console.WriteLine(csv);

更新された例:http : //rextester.com/YZCVXV6464


11
最初の説明のもう1つの説明は、提供する関数は、配列が1つの要素に縮小されるまで、常に最初の2つのメンバーを結合することです。そう[1,2,3,4]でしょう[3,3,4]、その後[6,4]、最後に[10]。ただし、単一の値の配列を返す代わりに、値そのものを取得します。
David Raab 2014

2
Aggregate関数を早期に中断/終了できますか?たとえば、chars.Aggregate((a、b)=> {if(a == 'a')は集計全体を分割し、それ以外はa + '、' + b}を返します)
Jeff Tian

13
@JeffTian-私はTakeWhile次にチェーンすることをお勧めしAggregateます-それはEnumerable拡張機能の素晴らしい機能です-それらは簡単にチェーン可能です。だからあなたはで終わるTakeWhile(a => a == 'a').Aggregate(....)。この例をご覧ください:rextester.com/WPRA60543
Jamiec '22

2
補遺の補足として、ブロック全体を簡単に置き換えることができますvar csv = string.Join(",", chars)(集約または文字列ビルダーは不要)。しかし、ええ、答えの要点は集約の使用例を示すことでした。ただし、文字列を結合するだけでは推奨されないことに言及したいと思います。そのための専用の方法がすでにあります...
T_D

2
別の一般的な使用(これまでのところ唯一の私も生産コードで見てきましたが)のようminまたはmaxのアイテムを取得することですvar biggestAccount = Accounts.Aggregate((a1, a2) => a1.Amount >= a2.Amount ? a1 : a2);
フランク・

133

それはあなたが話している過負荷に部分的に依存しますが、基本的な考え方は:

  • 「現在の値」としてのシードから始めます
  • シーケンスを反復します。シーケンスの各値について:
    • ユーザー指定の関数を適用して変換する (currentValue, sequenceValue)する(nextValue)
    • セットする currentValue = nextValue
  • 決勝を返す currentValue

あなたは見つけるかもしれません Aggregate私のEdulinqシリーズの投稿が役に立つ -より詳細な説明(さまざまなオーバーロードを含む)と実装が含まれています。

簡単な例の1つはAggregate、の代わりにを使用していCountます。

// 0 is the seed, and for each item, we effectively increment the current value.
// In this case we can ignore "item" itself.
int count = sequence.Aggregate(0, (current, item) => current + 1);

または、文字列のシーケンス内のすべての文字列の長さを合計することもできます。

int total = sequence.Aggregate(0, (current, item) => current + item.Length);

個人的に私が役立つことはめったにありませAggregate-「調整された」集約方法は通常私にとって十分です。


6
@Jonコア間で作業を分割できるようにアイテムをツリーに分割するAggregateの非同期バリエーションはありますか?メソッドのデザインは「削減」または「折りたたみ」の概念と一致しているようですが、それが本当に内部でそれを行っているのか、単にアイテムのリストを繰り返し処理しているのかはわかりません。
AaronLS

@ジョン:上記のedulinkが機能していません。正しいリンクにリダイレクトしてください。また、回答で使用した「調整された」集約関数という用語について、具体的に説明してください。
Koushik 2014年

1
@Koushik:投稿のリンクを修正しました。「調整された」集約関数とは、最大/最小/カウント/合計などを意味します。
Jon Skeet

62

スーパーショート アグリゲートは、Haskell / ML / F#のフォールドのように機能します。

少し長い .Max()、. Min()、. Sum()、. Average()はすべて、シーケンス内の要素を反復処理し、それぞれの集約関数を使用してそれらを集約します。.Aggregate()は、開発者が開始状態(別名シード)および集約関数を指定できるようにするという点で、一般化されたアグリゲーターです。

短い説明を求めていたのは知っていますが、他の人がいくつかの短い回答をしていたので、もう少し長い説明に興味があると思いました。

コード付きの長いバージョン 何が行われるかを示す1つの方法は、foreachを1回、.Aggregateを1回使用して、サンプル標準偏差を実装する方法を示すことができます。注:ここではパフォーマンスを優先していないため、不要なコレクションに対して数回繰り返します

最初に、2次距離の合計を作成するために使用されるヘルパー関数:

static double SumOfQuadraticDistance (double average, int value, double state)
{
    var diff = (value - average);
    return state + diff * diff;
}

次に、ForEachを使用して標準偏差をサンプリングします。

static double SampleStandardDeviation_ForEach (
    this IEnumerable<int> ints)
{
    var length = ints.Count ();
    if (length < 2)
    {
        return 0.0;
    }

    const double seed = 0.0;
    var average = ints.Average ();

    var state = seed;
    foreach (var value in ints)
    {
        state = SumOfQuadraticDistance (average, value, state);
    }
    var sumOfQuadraticDistance = state;

    return Math.Sqrt (sumOfQuadraticDistance / (length - 1));
}

次に、.Aggregateを使用します。

static double SampleStandardDeviation_Aggregate (
    this IEnumerable<int> ints)
{
    var length = ints.Count ();
    if (length < 2)
    {
        return 0.0;
    }

    const double seed = 0.0;
    var average = ints.Average ();

    var sumOfQuadraticDistance = ints
        .Aggregate (
            seed,
            (state, value) => SumOfQuadraticDistance (average, value, state)
            );

    return Math.Sqrt (sumOfQuadraticDistance / (length - 1));
}

これらの関数は、sumOfQuadraticDistanceの計算方法を除いて同じです。

var state = seed;
foreach (var value in ints)
{
    state = SumOfQuadraticDistance (average, value, state);
}
var sumOfQuadraticDistance = state;

対:

var sumOfQuadraticDistance = ints
    .Aggregate (
        seed,
        (state, value) => SumOfQuadraticDistance (average, value, state)
        );

したがって、.Aggregateが行うことは、このアグリゲーターパターンをカプセル化することであり、.Aggregateの実装は次のようになると期待しています。

public static TAggregate Aggregate<TAggregate, TValue> (
    this IEnumerable<TValue> values,
    TAggregate seed,
    Func<TAggregate, TValue, TAggregate> aggregator
    )
{
    var state = seed;

    foreach (var value in values)
    {
        state = aggregator (state, value);
    }

    return state;
}

標準偏差関数を使用すると、次のようになります。

var ints = new[] {3, 1, 4, 1, 5, 9, 2, 6, 5, 4};
var average = ints.Average ();
var sampleStandardDeviation = ints.SampleStandardDeviation_Aggregate ();
var sampleStandardDeviation2 = ints.SampleStandardDeviation_ForEach ();

Console.WriteLine (average);
Console.WriteLine (sampleStandardDeviation);
Console.WriteLine (sampleStandardDeviation2);

私見では

では、.Aggregateは読みやすさを向上させますか?.Where、.Select、.OrderByなどが読みやすさを大いに高めると思うので、一般に私はLINQが好きです(インライン化された階層の.Selectsを避ける場合)。Aggregateは完全性の理由からLinq内にある必要がありますが、個人的には、.Aggregateは適切に記述されたforeachと比較して読みやすさを向上させるとは確信していません。


+1すばらしい!しかし、拡張メソッドSampleStandardDeviation_Aggregate()SampleStandardDeviation_ForEach()することはできませんprivate(アクセス修飾子が存在しない場合にデフォルトでは)、そのいずれかによって計上されている必要がありますpublicinternal、それは私には思われる
Fulproof

参考:私のサンプルの拡張メソッドを正しく覚えている場合、この場合、拡張メソッドはそれらを使用した同じクラスの一部でした==>非公開の作品。
ちょうど別のメタ

39

写真は千の言葉に値する

注意:
Func<X, Y, R>はタイプXとの2つの入力を持つ関数で、タイプYの結果を返しますR

Enumerable.Aggregateには3つのオーバーロードがあります。


オーバーロード1:

A Aggregate<A>(IEnumerable<A> a, Func<A, A, A> f)

集計1

例:

new[]{1,2,3,4}.Aggregate((x, y) => x + y);  // 10


このオーバーロードは単純ですが、次の制限があります。

  • シーケンスには少なくとも1つの要素が含まれている必要があります。
    そうでない場合、関数はをスローしInvalidOperationExceptionます。
  • 要素と結果は同じ型でなければなりません。



オーバーロード2:

B Aggregate<A, B>(IEnumerable<A> a, B bIn, Func<B, A, B> f)

集計2

例:

var hayStack = new[] {"straw", "needle", "straw", "straw", "needle"};
var nNeedles = hayStack.Aggregate(0, (n, e) => e == "needle" ? n+1 : n);  // 2


このオーバーロードはより一般的です:

  • シード値を指定する必要があります(bIn)。
  • コレクションは空にすることができ、
    するこの場合、関数は結果としてシード値を生成します。
  • 要素と結果は異なるタイプを持つことができます。



オーバーロード3:

C Aggregate<A,B,C>(IEnumerable<A> a, B bIn, Func<B,A,B> f, Func<B,C> f2)


3番目のオーバーロードは、あまり有用なIMOではありません。
同じことは、結果を変換する関数が後に続くオーバーロード2を使用することで、より簡潔に書くことができます。


イラストは、この優れたブログポストから採用されました


これは、Haskelに関する質問に最適です。しかしAggegate、.net のオーバーロードはありませんFunc<T, T, T>
Jamiec 2017

4
はい、あります。あなた自身の答えでそれを使用します!
3dGrabber 2017

1
シーケンスが空のときに何が起こるかを注意深く説明するため、賛成票を投じます。してみましょうNは、ソース内の要素の数です。aをとらないオーバーロードseedは、アキュムレータ関数をN -1回適用することがわかります。他のオーバーロード(それはを受け取るseed)は、アキュムレータ関数をN回適用します。
Jeppe Stig Nielsen

17

集計は、基本的にデータのグループ化または合計に使用されます。

MSDNによると、「集計関数はシーケンスにアキュムレータ関数を適用します。」

例1:すべての数値を配列に追加します。

int[] numbers = new int[] { 1,2,3,4,5 };
int aggregatedValue = numbers.Aggregate((total, nextValue) => total + nextValue);

*重要:デフォルトの初期集計値は、コレクションのシーケンスの1要素です。つまり、変数の初期値の合計はデフォルトで1になります。

変数の説明

合計:関数によって返される合計値(集計値)を保持します。

nextValue:配列シーケンスの次の値です。この値は、合計値、つまり合計に追加されます。

例2:すべての項目を配列に追加します。また、アキュムレータの初期値を設定して、10から追加を開始します。

int[] numbers = new int[] { 1,2,3,4,5 };
int aggregatedValue = numbers.Aggregate(10, (total, nextValue) => total + nextValue);

引数の説明:

最初の引数は、配列の次の値で加算を開始するために使用される初期値(開始値、つまりシード値)です。

2番目の引数はfuncであり、2 intを取るfuncです。

1.合計:これは、計算後にfuncによって返される合計値(集約値)の前と同じです。

2.nextValue::配列シーケンスの次の値です。この値は、合計値、つまり合計に追加されます。

また、このコードをデバッグすると、集計がどのように機能するかをよりよく理解できます。


7

ジャミエックの答えから多くを学びました。

CSV文字列の生成のみが必要な場合は、これを試すことができます。

var csv3 = string.Join(",",chars);

これは100万の文字列を使ったテストです

0.28 seconds = Aggregate w/ String Builder 
0.30 seconds = String.Join 

ソースコードはこちら


リンクで提供されているのと同じコードをdotnetfiddle.netで実行すると、「string.Join」に対して「致命的エラー:メモリ使用制限を超えました」が表示されましたが、Aggregateは常に期待どおりに機能しました。私は信じているので、これはString.Join使用することは推奨されていません
マニッシュ・ジャイン

おかしい?Aggregate向けのFirst Stop Watchにコメントしたところ、その後、「致命的なエラー:メモリ使用量の制限を超えました」というメッセージは表示されません。説明してください!リンク:dotnetfiddle.net/6YyumS
Manish Jain

dotnetfiddle.netには、実行停止に達するとメモリ制限があります。集計コードをString.Joinコードの前に移動すると、集計エラーが発生する場合があります。
Rm558

7

ここでのすべてのすばらしい答えに加えて、私はそれを使用して、一連の変換ステップを通じてアイテムをウォークスルーしました。

変換がとして実装されている場合、Func<T,T>にいくつかの変換を追加し、List<Func<T,T>>を使用AggregateしてのインスタンスTを各ステップでウォークすることができます。

より具体的な例

string値を取得して、プログラムで構築できる一連のテキスト変換を実行したいとします。

var transformationPipeLine = new List<Func<string, string>>();
transformationPipeLine.Add((input) => input.Trim());
transformationPipeLine.Add((input) => input.Substring(1));
transformationPipeLine.Add((input) => input.Substring(0, input.Length - 1));
transformationPipeLine.Add((input) => input.ToUpper());

var text = "    cat   ";
var output = transformationPipeLine.Aggregate(text, (input, transform)=> transform(input));
Console.WriteLine(output);

これにより、一連の変換が作成されます。先頭と末尾のスペースを削除->最初の文字を削除->最後の文字を削除->大文字に変換 このチェーンのステップは、必要に応じて追加、削除、または並べ替えて、必要な変換パイプラインを作成できます。

この特定のパイプラインの最終結果は、に" cat "なり"A"ます。


これはTもよいことに気づくと、非常に強力になる可能性があります。これはBitMap、例として、フィルターのような画像変換に使用できます。


4

定義

集約メソッドは、ジェネリックコレクションの拡張メソッドです。Aggregateメソッドは、コレクションの各アイテムに関数を適用します。関数を適用するだけでなく、その結果を次の反復の初期値として使用します。したがって、結果として、コレクションから計算値(最小、最大、平均、またはその他の統計値)を取得します。

したがって、Aggregateメソッドは再帰関数の安全な実装の形式です。

再帰はコレクションの各項目を反復し、誤った終了条件によって無限ループの中断を取得できないため、安全です。現在の関数の結果が次の関数呼び出しのパラメーターとして使用されるため、再帰的

構文:

collection.Aggregate(seed, func, resultSelector);
  • シード -デフォルトの初期値。
  • func-再帰関数。ラムダ式、Funcデリゲート、関数タイプTF(T結果、T nextValue)のいずれかです。
  • resultSelector-最終結果を計算、変換、変更、変換するためのfuncまたは式のような関数を使用できます。

使い方:

var nums = new[]{1, 2};
var result = nums.Aggregate(1, (result, n) => result + n); //result = (1 + 1) + 2 = 4
var result2 = nums.Aggregate(0, (result, n) => result + n, response => (decimal)response/2.0); //result2 = ((0 + 1) + 2)*1.0/2.0 = 3*1.0/2.0 = 3.0/2.0 = 1.5

実用的な使い方:

  1. 数値nから階乗を求めます。

int n = 7;
var numbers = Enumerable.Range(1, n);
var factorial = numbers.Aggregate((result, x) => result * x);

これはこの関数と同じことをしています:

public static int Factorial(int n)
{
   if (n < 1) return 1;

   return n * Factorial(n - 1);
}
  1. Aggregate()は、Select()やWhere()などの最も強力なLINQ拡張メソッドの1つです。これを使用して、Sum()、Min()を置き換えることができます。Max()、Avg()機能、または追加のコンテキストを実装して変更する場合:
    var numbers = new[]{3, 2, 6, 4, 9, 5, 7};
    var avg = numbers.Aggregate(0.0, (result, x) => result + x, response => (double)response/(double)numbers.Count());
    var min = numbers.Aggregate((result, x) => (result < x)? result: x);
  1. 拡張メソッドのより複雑な使用法:
    var path = @“c:\path-to-folder”;

    string[] txtFiles = Directory.GetFiles(path).Where(f => f.EndsWith(“.txt”)).ToArray<string>();
    var output = txtFiles.Select(f => File.ReadAllText(f, Encoding.Default)).Aggregate<string>((result, content) => result + content);

    File.WriteAllText(path + summary.txt”, output, Encoding.Default);

    Console.WriteLine(“Text files merged into: {0}”, output); //or other log info

かなり良い最初の答え。よくやった!残念なことに、これはとても古い質問です。そうでなければ、賛成票がたくさんあります
Jamiec

1

これは、AggregateLinq SortingなどのFluent APIでの使用に関する説明です。

var list = new List<Student>();
var sorted = list
    .OrderBy(s => s.LastName)
    .ThenBy(s => s.FirstName)
    .ThenBy(s => s.Age)
    .ThenBy(s => s.Grading)
    .ThenBy(s => s.TotalCourses);

そして、一連のフィールドを取得するソート関数を実装したいことを見てみましょう。これはAggregate、次のようにforループの代わりに使用すると非常に簡単です。

public static IOrderedEnumerable<Student> MySort(
    this List<Student> list,
    params Func<Student, object>[] fields)
{
    var firstField = fields.First();
    var otherFields = fields.Skip(1);

    var init = list.OrderBy(firstField);
    return otherFields.Skip(1).Aggregate(init, (resultList, current) => resultList.ThenBy(current));
}

そして、次のように使用できます。

var sorted = list.MySort(
    s => s.LastName,
    s => s.FirstName,
    s => s.Age,
    s => s.Grading,
    s => s.TotalCourses);

1

誰もが彼の説明をしました。私の説明はそのようです。

Aggregateメソッドは、コレクションの各アイテムに関数を適用します。たとえば、コレクション{6、2、8、3}と、関数Add(operator +)を実行して(((6 + 2)+8)+3)とし、19を返します。

var numbers = new List<int> { 6, 2, 8, 3 };
int sum = numbers.Aggregate(func: (result, item) => result + item);
// sum: (((6+2)+8)+3) = 19

この例では、ラムダ式の代わりに名前付きメソッドAddが渡されます。

var numbers = new List<int> { 6, 2, 8, 3 };
int sum = numbers.Aggregate(func: Add);
// sum: (((6+2)+8)+3) = 19

private static int Add(int x, int y) { return x + y; }

0

Linq Aggregate拡張メソッドは、リストの要素に適用される一種の再帰関数を宣言することを可能にします。そのオペランドは2つです:リストに存在する順序での要素、一度に1つの要素、および以前の再帰反復の結果、またはまだ再帰でない場合は何もありません。

このようにして、数値の階乗を計算したり、文字列を連結したりできます。


0

多次元整数配列の列を合計するために使用される集計

        int[][] nonMagicSquare =
        {
            new int[] {  3,  1,  7,  8 },
            new int[] {  2,  4, 16,  5 },
            new int[] { 11,  6, 12, 15 },
            new int[] {  9, 13, 10, 14 }
        };

        IEnumerable<int> rowSums = nonMagicSquare
            .Select(row => row.Sum());
        IEnumerable<int> colSums = nonMagicSquare
            .Aggregate(
                (priorSums, currentRow) =>
                    priorSums.Select((priorSum, index) => priorSum + currentRow[index]).ToArray()
                );

Select with indexはAggregate func内で使用され、一致する列を合計して新しい配列を返します。{3 + 2 = 5、1 + 4 = 5、7 + 16 = 23、8 + 5 = 13}。

        Console.WriteLine("rowSums: " + string.Join(", ", rowSums)); // rowSums: 19, 27, 44, 46
        Console.WriteLine("colSums: " + string.Join(", ", colSums)); // colSums: 25, 24, 45, 42

しかし、ブール型配列の真の数を数えることは、蓄積された型(int)がソース型(bool)と異なるため、より困難です。ここでは、2番目のオーバーロードを使用するためにシードが必要です。

        bool[][] booleanTable =
        {
            new bool[] { true, true, true, false },
            new bool[] { false, false, false, true },
            new bool[] { true, false, false, true },
            new bool[] { true, true, false, false }
        };

        IEnumerable<int> rowCounts = booleanTable
            .Select(row => row.Select(value => value ? 1 : 0).Sum());
        IEnumerable<int> seed = new int[booleanTable.First().Length];
        IEnumerable<int> colCounts = booleanTable
            .Aggregate(seed,
                (priorSums, currentRow) =>
                    priorSums.Select((priorSum, index) => priorSum + (currentRow[index] ? 1 : 0)).ToArray()
                );

        Console.WriteLine("rowCounts: " + string.Join(", ", rowCounts)); // rowCounts: 3, 1, 2, 2
        Console.WriteLine("colCounts: " + string.Join(", ", colCounts)); // colCounts: 3, 2, 1, 2
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.