web-dev-qa-db-ja.com

LINQ集約アルゴリズムの説明

これは足りないように思えるかもしれませんが、私はAggregateの本当に良い説明を見つけることができませんでした。

良いとは、短く、説明的で、小さくて明確な例を用いて包括的であることを意味します。

654

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)

これは1を作るために23を追加します。それから3(前の結果)と3(シーケンスの次の要素)を追加して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で連結してa,b,cを作ります。等々。

例3.シードを使った数の乗算

完全を期すために、シード値をとるAggregateoverload があります。

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/ja-jp/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

944
Jamiec

それは部分的にはあなたが話している過負荷によって異なりますが、基本的な考え方は以下のとおりです。

  • 「現在値」としてシードから始めます
  • シーケンスを繰り返します。シーケンス内の各値に対して:
    • (currentValue, sequenceValue)(nextValue)に変換するためにユーザー指定の関数を適用します
    • currentValue = nextValueを設定
  • 最後のcurrentValueを返します

あなたは私のEdulinqシリーズの Aggregateの投稿を見つけることができます 役に立つ - それはより詳細な説明(さまざまなオーバーロードを含む)と実装を含みます。

簡単な例の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);

個人的にはI まれに Aggregateが便利であることがわかります - "あつらえた"集約方法は通常私には十分です。

126
Jon Skeet

スーパーショート Haskell/ML/F#では、集計はfoldのように機能します。

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

あなたが短い説明を求めたことを私は知っていますが、私はあなたがおそらくもう少し長いものに興味があるだろうと考えた2、3の短い答えを出したので考えました

コード付きロングバージョン どのように実装するのかを示すための1つの方法 サンプル標準偏差 かつてforeachを使用し、かつ一度.Aggregateを使用する。 注:ここではパフォーマンスに優先順位を付けていないので、不要なことに収集について複数回繰り返します

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

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);

_ imho _

それで、.Aggregateは読みやすさを助けますか?一般的に私はLINQが大好きです。なぜなら、.Select、.OrderByなどは読みやすさに大いに役立ちます(インラインの階層的な.Selectを避ける場合)。完全性のためにAggregateはLinqになければなりませんが、個人的には、.Aggregateがよく書かれたforeachと比較して読みやすさを増すとは思っていません。

絵は千語の価値がある

注意:Func<A, B, C>は、ABの2つの入力を持つ関数で、Cを返します。

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


過負荷1:

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

Aggregate1

例:

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)

Aggregate2

例:

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の後にその結果を変換する関数を続けることによって、より簡潔に書くことができます。


イラストは この素晴らしいブログ投稿 から改変されています。

31
3dGrabber

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

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

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

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

*重要:デフォルトの初期集約値は、一連の収集の中の1つの要素です。すなわち、合計変数初期値はデフォルトで1になります。

変数の説明

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

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

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

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

引数の説明:

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

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

1.total:計算後に関数によって返される合計値(集計値)の前と同じ値になります。

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

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

14
maxspan

Jamiecの answerから多くのことを学びました。

CSV文字列を生成することだけが必要な場合は、これを試してください。

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

これは100万弦のテストです。

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

ソースコードは ここ

7
Rm558

ここにあるすべての素晴らしい答えに加えて、私はまた、一連の変換ステップを介してアイテムを歩くためにそれを使用しました。

変換が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を使用して、これはフィルタのような画像変換に使用できます。

5
Bradley Uffner

短くて本質的な定義はこれであるかもしれません:Linq Aggregate拡張メソッドはリストの要素に適用される一種の再帰関数を宣言することを可能にします。一度に1つの要素、および前の再帰的反復の結果、またはまだ再帰的でない場合は何もしません。

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

0
Ciro Corvino

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

集約メソッドは、コレクションの各項目に関数を適用します。たとえば、コレクション{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
user2983359

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

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);

そして、一連のフィールドをとるソート関数を実装したいのですが、これはforループの代わりにAggregateを使うのがとても簡単です。

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);
0
Jaider

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

        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()
                );

一致する列を合計して新しい配列を返すために、Aggregate関数内でselect with indexが使用されます。 {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
0
Dan M