850 Stimmen

LINQ Aggregate-Algorithmus erklärt

Das mag lahm klingen, aber ich habe noch keine wirklich gute Erklärung gefunden für Aggregate .

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

1178voto

Jamiec Punkte 127963

Die am einfachsten zu verstehende Definition von Aggregate besteht darin, dass es an jedem Element der Liste eine Operation durchführt und dabei die vorangegangenen Operationen berücksichtigt. Das heißt, sie führt die Operation am ersten und zweiten Element durch und trägt das Ergebnis weiter. Dann führt es die Operation am vorherigen Ergebnis und am dritten Element durch und trägt das Ergebnis weiter. usw.

Beispiel 1. Summieren von Zahlen

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 hinzu 1 y 2 zu machen 3 . Dann fügt er hinzu 3 (Ergebnis der vorherigen) und 3 (nächstes Element in der Reihenfolge) zu machen 6 . Dann fügt er hinzu 6 y 4 zu machen 10 .

Beispiel 2. Erstellen einer csv-Datei aus einem Array von Strings

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

Dies funktioniert auf ähnliche Weise. Verketten a ein Komma und b zu machen a,b . Dann verkettet a,b mit einem Komma und c zu machen a,b,c . und so weiter.

Beispiel 3. Multiplikation von Zahlen mit einem Seed

Der Vollständigkeit halber sei erwähnt, dass es eine Überlastung de Aggregate die 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 es mit dem ersten Element der Folge 10 mit einem Ergebnis von 50 . Dieses Ergebnis wird vorgetragen und mit der nächsten Zahl in der Folge multipliziert 20 zu einem Ergebnis von 1000 . Dies setzt sich für die restlichen 2 Elemente der Sequenz fort.

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


Nachtrag

Im obigen Beispiel 2 wird die Verkettung von Zeichenfolgen verwendet, um eine Liste von Werten zu erstellen, die durch ein Komma getrennt sind. Dies ist eine vereinfachte Art, die Verwendung von Aggregate was die Absicht dieser Antwort war. Wenn diese Technik jedoch verwendet wird, um eine große Menge an kommagetrennten Daten zu erstellen, wäre es sinnvoller, eine StringBuilder und dies ist durchaus vereinbar mit Aggregate unter Verwendung der gesetzten Überlast zur Auslösung der 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);

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

150voto

Jon Skeet Punkte 1325502

Es hängt zum Teil davon ab, von welcher Überlastung man spricht, aber die Grundidee ist die gleiche:

  • Beginnen Sie mit einem Startwert als "aktuellen Wert".
  • Iterieren Sie über die Sequenz. Für jeden Wert in der Sequenz:
    • Anwendung einer benutzerdefinierten Funktion zur Transformation (currentValue, sequenceValue) in (nextValue)
    • Satz currentValue = nextValue
  • Rückgabe der endgültigen currentValue

Vielleicht finden Sie die Aggregate Beitrag in meiner Edulinq-Serie nützlich - sie enthält eine ausführlichere 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 die Summierung aller Längen von Zeichenketten in einer Folge von Zeichenketten:

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

Ich persönlich selten めっけもの Aggregate nützlich - die "maßgeschneiderten" Aggregationsmethoden sind für mich in der Regel gut genug.

65voto

Super kurz Aggregate funktioniert wie fold in Haskell/ML/F#.

Geringfügig länger .Max(), .Min(), .Sum(), .Average() iteriert über die Elemente einer Sequenz und aggregiert sie mit der jeweiligen Aggregatfunktion. .Aggregate () ist ein verallgemeinerter Aggregator, der es dem Entwickler erlaubt, den Startzustand (auch Seed genannt) und die Aggregatfunktion anzugeben.

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

Langfassung mit Code Eine Möglichkeit zur Veranschaulichung könnte sein, zu zeigen, wie Sie die Probe Standardabweichung einmal mit foreach und einmal mit .Aggregate. Hinweis: Ich habe die Leistung hier nicht priorisiert, so dass ich die Sammlung unnötigerweise mehrmals wiederhole.

Zunächst eine Hilfsfunktion zur Erstellung einer Summe quadratischer Abstände:

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

Dann Stichprobe 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 Art und Weise, wie sumOfQuadraticDistance berechnet wird, identisch sind:

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

Versus:

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

Aggregate kapselt also dieses Aggregator-Muster und ich erwarte, dass die Implementierung von Aggregate in etwa so aussehen wird:

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 etwa 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 der Lesbarkeit? Im Allgemeinen liebe ich LINQ, weil ich denke, dass .Where, .Select, .OrderBy und so weiter die Lesbarkeit erheblich verbessern (wenn man inlined hierarhische .Selects vermeidet). Aggregate müssen aus Gründen der Vollständigkeit in Linq enthalten sein, aber ich persönlich bin nicht davon überzeugt, dass .Aggregate die Lesbarkeit im Vergleich zu einem gut geschriebenen foreach verbessert.

58voto

3dGrabber Punkte 4250

Ein Bild sagt mehr als tausend Worte

Zur Erinnerung:
Func<X, Y, R> ist eine Funktion mit zwei Eingängen des Typs X y Y die ein Ergebnis vom Typ R .

Enumerable.Aggregate hat drei Überladungen:

Überlastung 1:

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

Aggregate1

Exemple :

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

Diese Überlastung ist einfach, hat aber die folgenden Einschränkungen:

  • die Folge muss mindestens ein Element enthalten,
    sonst löst die Funktion eine InvalidOperationException .
  • Elemente und Ergebnis müssen vom gleichen Typ sein.

Überlastung 2:

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

Aggregate2

Exemple :

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

Diese Überlastung ist eher allgemeiner Natur:

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

Überlastung 3:

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

Die dritte Überlast ist IMO nicht sehr nützlich.
Das Gleiche lässt sich knapper formulieren, indem man die Überladung 2 verwendet, gefolgt von einer Funktion, die ihr Ergebnis umwandelt.

Die Illustrationen sind angepasst von dieser ausgezeichnete Blogpost .

18voto

maxspan Punkte 12034

Das Aggregat wird grundsätzlich zur Gruppierung oder Zusammenfassung von Daten verwendet.

A "Aggregate Function Wendet eine Akkumulatorfunktion auf eine Sequenz an."

Beispiel 1: Addiere alle Zahlen in einer Reihe.

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

*Wichtig: Der anfängliche Aggregatwert ist standardmäßig das 1 Element in der Auflistungsfolge. D.h.: der Anfangswert der Gesamtvariablen ist standardmäßig 1.

variable Erklärung

total: enthält den von der Funktion zurückgegebenen Summenwert (aggregierter Wert).

nextValue: Es handelt sich um den nächsten Wert in der Array-Sequenz. Dieser Wert wird dann zu dem aggregierten Wert, d.h. der Gesamtsumme, addiert.

Beispiel 2: Hinzufügen aller Elemente in einem Array. Legen Sie auch den anfänglichen Akkumulatorwert fest, 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ären:

das erste Argument ist der Initialwert (Startwert, d.h. Seed-Wert), der verwendet wird, um die Addition mit dem nächsten Wert im Array zu beginnen.

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

1.total: Hier wird wie zuvor der von der Funktion nach der Berechnung zurückgegebene Summenwert (aggregierter Wert) gespeichert.

2.nextValue: : Es handelt sich um den nächsten Wert in der Array-Sequenz. Dieser Wert wird dann zu dem aggregierten Wert, d.h. der Gesamtsumme, addiert.

Auch das Debuggen dieses Codes wird Ihnen ein besseres Verständnis für die Funktionsweise von Aggregaten vermitteln.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X