it-swarm.com.de

LINQ Aggregate Algorithmus erklärt

Das mag lahm klingen, aber ich konnte keine wirklich gute Erklärung für Aggregate finden.

Gut bedeutet kurz, beschreibend, umfassend mit einem kleinen und klaren Beispiel.

673

Die am einfachsten zu verstehende Definition von Aggregate besteht darin, dass für jedes Element der Liste eine Operation ausgeführt wird, wobei die zuvor ausgeführten Operationen berücksichtigt werden. Das heißt, es führt die Aktion für das erste und das zweite Element aus und überträgt das Ergebnis. Dann bearbeitet es das vorherige Ergebnis und das dritte Element und überträgt es. usw.

Beispiel 1. Zahlen summieren

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

Dies fügt 1 und 2 hinzu, um 3 zu erstellen. Addiert dann 3 (Ergebnis von vorherigem) und 3 (nächstes Element in Folge), um 6 zu erstellen. Dann werden 6 und 4 hinzugefügt, um 10 zu erstellen.

Beispiel 2. Erstellen Sie eine CSV aus einem Array von Zeichenfolgen

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

Das funktioniert ähnlich. Verketten Sie a mit einem Komma und b, um a,b zu erstellen. Verkettet dann a,b mit einem Komma und c, um a,b,c zu erstellen. und so weiter.

Beispiel 3. Multiplizieren von Zahlen mit einem Startwert

Der Vollständigkeit halber gibt es ein Überladung von Aggregate, das einen Startwert annimmt.

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)

Ähnlich wie in den obigen Beispielen beginnt dies mit einem Wert von 5 und multipliziert ihn mit dem ersten Element der Sequenz 10, was ein Ergebnis von 50 ergibt. Dieses Ergebnis wird übertragen und mit der nächsten Zahl in der Sequenz 20 multipliziert, um ein Ergebnis von 1000 zu erhalten. Dies setzt sich durch die restlichen 2 Elemente der Sequenz fort.

Live-Beispiele: http://rextester.com/ZXZ64749
Docs: http://msdn.Microsoft.com/en-us/library/bb548651.aspx


Nachtrag

In Beispiel 2 oben wird mithilfe der Zeichenfolgenverkettung eine Liste von Werten erstellt, die durch ein Komma getrennt sind. Dies ist eine vereinfachte Methode, um die Verwendung von Aggregate zu erklären, die die Absicht dieser Antwort war. Wenn Sie diese Technik jedoch verwenden, um eine große Menge von durch Kommas getrennten Daten zu erstellen, ist es sinnvoller, einen StringBuilder zu verwenden. Dies ist vollständig kompatibel mit Aggregate, wobei die gesetzte Überladung verwendet wird, um den StringBuilder zu initiieren.

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

Aktualisiertes Beispiel: http://rextester.com/YZCVXV6464

966
Jamiec

Es hängt zum Teil davon ab, von welcher Überlastung Sie sprechen, aber die Grundidee ist:

  • Beginnen Sie mit einem Startwert als "aktuellem Wert"
  • Durchlaufen Sie die Sequenz. Für jeden Wert in der Sequenz:
    • Wenden Sie eine benutzerdefinierte Funktion an, um (currentValue, sequenceValue) in (nextValue) zu transformieren.
    • currentValue = nextValue einstellen
  • Gib das letzte currentValue zurück

Sie finden den Aggregate -Post in meiner Edulinq-Reihe nützlich - er enthält eine detailliertere Beschreibung (einschließlich der verschiedenen Überladungen) und Implementierungen.

Ein einfaches Beispiel ist die Verwendung von Aggregate als Alternative zu 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);

Oder vielleicht alle Längen von Strings in einer Folge von Strings summieren:

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

Persönlich finde ich selten Aggregate nützlich - die "maßgeschneiderten" Aggregationsmethoden sind normalerweise gut genug für mich.

127
Jon Skeet

Super Short Aggregat funktioniert wie Fold in Haskell/ML/F #.

Etwas länger .Max (), .Min (), .Sum (), .Average () durchläuft alle Elemente in einer Sequenz und aggregiert sie mit der jeweiligen Aggregatfunktion. .Aggregate () ist ein verallgemeinerter Aggregator, mit dem der Entwickler den Startstatus (auch als Startwert bezeichnet) und die Aggregatfunktion angeben kann.

Ich weiß, dass Sie um eine kurze Erklärung gebeten haben, aber ich dachte, als andere ein paar kurze Antworten gaben, dachte ich, Sie wären vielleicht an einer etwas längeren interessiert.

Lange Version mit Code Eine Möglichkeit zu veranschaulichen, wie dies funktioniert, ist, zu zeigen, wie Sie Beispielstandardabweichung einmal foreach und einmal foreach verwenden using .Aggregate. Hinweis: Ich habe die Leistung hier nicht priorisiert, daher iteriere ich unnötigerweise mehrmals über die Sammlung.

Zuerst wird eine Hilfsfunktion verwendet, um eine Summe quadratischer Abstände zu erstellen:

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

Dann probiere Standardabweichung mit 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));
}

Dann einmal mit .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));
}

Beachten Sie, dass diese Funktionen bis auf die Berechnung von sumOfQuadraticDistance identisch sind:

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

Gegen:

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

Was .Aggregate also tut, ist, dass es dieses Aggregatormuster kapselt, und ich gehe davon aus, dass die Implementierung von .Aggregate ungefähr so ​​aussehen würde:

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

Die Verwendung der Standardabweichungsfunktionen würde ungefähr so ​​aussehen:

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

Hilft .Aggregate also bei der Lesbarkeit? Im Allgemeinen liebe ich LINQ, weil ich denke, dass .Where, .Select, .OrderBy und so weiter die Lesbarkeit erheblich verbessern (wenn Sie inline hierarchische .Selects vermeiden). Aggregate muss aus Gründen der Vollständigkeit in Linq sein, aber ich persönlich bin nicht so überzeugt, dass .Aggregate die Lesbarkeit im Vergleich zu einem gut geschriebenen foreach erhöht.

Ein Bild sagt mehr als tausend Worte

Erinnerung:
Func<X, Y, R> ist eine Funktion mit zwei Eingängen vom Typ X und Y, die ein Ergebnis vom Typ R zurückgibt.

Enumerable.Aggregate hat drei Überladungen:


Überlast 1:

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

Aggregate1

Beispiel:

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


Diese Überladung ist einfach, weist jedoch die folgenden Einschränkungen auf:

  • die Sequenz muss mindestens ein Element enthalten,
    Andernfalls wirft die Funktion ein InvalidOperationException.
  • elemente und Ergebnis müssen vom selben Typ sein.



Überlast 2:

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

Aggregate2

Beispiel:

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


Diese Überladung ist allgemeiner:

  • es muss ein Startwert angegeben werden (bIn).
  • die Sammlung kann leer sein,
    In diesem Fall liefert die Funktion den Startwert als Ergebnis.
  • elemente und Ergebnis können unterschiedliche Typen haben.



Überlast 3:

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


Die dritte Überladung ist nicht sehr nützlich, IMO.
Das Gleiche kann prägnanter geschrieben werden, indem Überladung 2 gefolgt von einer Funktion verwendet wird, die das Ergebnis transformiert.


Die Illustrationen sind von diesem ausgezeichneten Blogpost angepasst.

34
3dGrabber

Aggregat wird grundsätzlich zum Gruppieren oder Zusammenfassen von Daten verwendet.

Laut MSDN "Aggregate Function Wendet eine Akkumulatorfunktion über eine Sequenz an."

Beispiel 1: Fügen Sie alle Zahlen in einem Array hinzu.

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

* Wichtig: Der anfängliche Gesamtwert ist standardmäßig das Element 1 in der Auflistungssequenz. das heißt: der anfängliche Wert der Gesamtvariablen ist standardmäßig 1.

variable Erklärung

total: Es wird der von der Funk zurückgegebene Summenwert (aggregierter Wert) gespeichert.

nextValue: Dies ist der nächste Wert in der Array-Sequenz. Dieser Wert wird dann zu dem aggregierten Wert addiert, d.h.

Beispiel 2: Fügen Sie alle Elemente in einem Array hinzu. Stellen Sie auch den anfänglichen Akkumulatorwert ein, um mit der Addition von 10 zu beginnen.

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

argumente Erklärung:

das erste Argument ist der Anfangswert (Startwert, dh der Startwert), der verwendet wird, um die Addition mit dem nächsten Wert im Array zu starten.

das zweite Argument ist eine Funktion, die 2 int benötigt.

1.total: Dies gilt genauso wie vor dem Summenwert (aggregierter Wert), den die Funk nach der Berechnung zurückgibt.

2.nextValue:: Dies ist der nächste Wert in der Array-Sequenz. Dieser Wert wird dann zu dem aggregierten Wert addiert, d.h.

Durch das Debuggen dieses Codes erhalten Sie auch ein besseres Verständnis der Funktionsweise von Aggregaten.

15
maxspan

Viel gelernt von Jamiecs Antwort.

Wenn Sie nur eine CSV-Zeichenfolge generieren müssen, können Sie dies versuchen.

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

Hier ist ein Test mit 1 Million Zeichenketten

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

Der Quellcode ist hier

7
Rm558

Zusätzlich zu all den tollen Antworten hier habe ich es auch verwendet, um einen Gegenstand durch eine Reihe von Transformationsschritten zu führen.

Wenn eine Transformation als Func<T,T> implementiert ist, können Sie einem List<Func<T,T>> mehrere Transformationen hinzufügen und Aggregate verwenden, um eine Instanz von T durch jeden Schritt zu führen.

Ein konkreteres Beispiel

Sie möchten einen string -Wert verwenden und ihn durch eine Reihe von Texttransformationen führen, die programmgesteuert erstellt werden können.

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

Dadurch wird eine Transformationskette erstellt: Führende und nachfolgende Leerzeichen entfernen -> erstes Zeichen entfernen -> letztes Zeichen entfernen -> in Großbuchstaben konvertieren. Schritte in dieser Kette können nach Bedarf hinzugefügt, entfernt oder neu angeordnet werden, um die erforderliche Transformationspipeline zu erstellen.

Das Endergebnis dieser speziellen Pipeline ist, dass " cat " zu "A" wird.


Dies kann sehr mächtig werden, wenn Sie erkennen, dass Talles sein kann. Dies könnte für Bildtransformationen wie Filter verwendet werden, wobei BitMap als Beispiel dient.

6
Bradley Uffner

Jeder hat seine Erklärung gegeben. Meine Erklärung ist so.

Die Aggregatmethode wendet eine Funktion auf jedes Element einer Sammlung an. Nehmen wir zum Beispiel die Sammlung {6, 2, 8, 3} und die Funktion Add (operator +) (((6 + 2) +8) +3) und geben 19 zurück

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

In diesem Beispiel wird anstelle des Lambda-Ausdrucks die benannte Methode Add übergeben.

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; }
1
user2983359

Eine kurze und wichtige Definition könnte sein: Die Erweiterungsmethode von Linq Aggregate ermöglicht die Deklaration einer Art rekursiver Funktion, die auf die Elemente einer Liste angewendet wird, deren Operanden zwei sind: Die Elemente in der Reihenfolge, in der sie in der Liste vorhanden sind. ein Element zu einem Zeitpunkt und das Ergebnis der vorherigen rekursiven Iteration oder nichts, wenn noch nicht, Rekursion.

Auf diese Weise können Sie die Fakultät von Zahlen berechnen oder Zeichenfolgen verketten.

0
Ciro Corvino

Dies ist eine Erklärung zur Verwendung von Aggregate in einer Fluent-API wie der Linq-Sortierung.

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

und mal sehen, wir wollen eine Sortierfunktion implementieren, die eine Reihe von Feldern akzeptiert. Dies ist sehr einfach, wenn Sie Aggregate anstelle einer for-Schleife verwenden, wie folgt:

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

Und wir können es so benutzen:

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

Aggregat zum Summieren von Spalten in einem mehrdimensionalen Integer-Array

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

Mit Index auswählen wird in der Aggregatfunktion verwendet, um die übereinstimmenden Spalten zu summieren und ein neues Array zurückzugeben. {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

Das Zählen der Anzahl der Wahrheiten in einem Booleschen Array ist jedoch schwieriger, da sich der akkumulierte Typ (int) vom Quelltyp (bool) unterscheidet. hier ist ein seed notwendig, um die zweite überlastung zu nutzen.

        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