Question Explication de l'algorithme d'agrégat LINQ


Cela peut sembler boiteux, mais je n'ai pas été en mesure de trouver une très bonne explication de Aggregate.

Bon moyen court, descriptif, complet avec un exemple petit et clair.


574
2017-08-18 09:51


origine


Réponses:


La définition la plus facile à comprendre Aggregate est qu'il effectue une opération sur chaque élément de la liste en tenant compte des opérations antérieures. C'est-à-dire qu'il effectue l'action sur le premier et le deuxième élément et porte le résultat en avant. Ensuite, il fonctionne sur le résultat précédent et le troisième élément et continue. etc.

Exemple 1. Numéros de sommation

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

Cela ajoute 1 et 2 faire 3. Puis ajoute 3 (résultat du précédent) et 3 (élément suivant dans l'ordre) pour faire 6. Puis ajoute 6 et 4 faire 10.

Exemple 2. créer un CSV à partir d'un tableau de chaînes

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

Cela fonctionne de la même manière. Enchaîner a une virgule et b faire a,b. Puis concatène a,b  avec une virgule et c faire a,b,c. etc.

Exemple 3. Multiplier des nombres en utilisant une graine

Pour être complet, il y a un surcharge de Aggregate qui prend une valeur de graine.

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)

Tout comme les exemples ci-dessus, cela commence avec une valeur de 5 et le multiplie par le premier élément de la séquence 10 donner un résultat de 50. Ce résultat est reporté et multiplié par le nombre suivant dans la séquence 20 pour donner un résultat de 1000. Cela continue à travers les 2 éléments restants de la séquence.

Exemples en direct: http://rextester.com/ZXZ64749
Docs: http://msdn.microsoft.com/en-us/library/bb548651.aspx


Addenda

L'exemple 2 ci-dessus utilise la concaténation de chaîne pour créer une liste de valeurs séparées par une virgule. C'est une manière simpliste d'expliquer l'utilisation de Aggregate ce qui était l'intention de cette réponse. Cependant, si vous utilisez cette technique pour créer réellement une grande quantité de données séparées par des virgules, il serait plus approprié d'utiliser un StringBuilder, et ceci est entièrement compatible avec Aggregate en utilisant la surcharge ensemencée pour initier le 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);

Exemple mis à jour: http://rextester.com/YZCVXV6464


849
2017-08-18 09:59



Cela dépend en partie de la surcharge dont vous parlez, mais l'idée de base est la suivante:

  • Commencez avec une graine comme "valeur actuelle"
  • Itérer sur la séquence. Pour chaque valeur de la séquence:
    • Appliquer une fonction spécifiée par l'utilisateur pour transformer (currentValue, sequenceValue) dans (nextValue)
    • Ensemble currentValue = nextValue
  • Retourner la finale currentValue

Vous pouvez trouver le Aggregate poster dans ma série Edulinq utile - il comprend une description plus détaillée (y compris les différentes surcharges) et les implémentations.

Un exemple simple utilise Aggregate comme une alternative à 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);

Ou peut-être additionner toutes les longueurs de chaînes dans une séquence de chaînes:

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

Personnellement je rarement trouver Aggregate utile - les méthodes d'agrégation "sur mesure" sont généralement assez bonnes pour moi.


114
2017-08-18 09:54



Super court  L'agrégat fonctionne comme plier dans Haskell / ML / F #.

Un petit peu plus long .Max (), .Min (), .Sum (), .Average () toutes les itérations sur les éléments d'une séquence et les agrège en utilisant la fonction d'agrégat respective. .Aggregate () est un agrégateur généralisé en ce sens qu'il permet au développeur de spécifier l'état de départ (alias seed) et la fonction d'agrégation.

Je sais que vous avez demandé une courte explication, mais je me suis dit que d'autres personnes ont donné quelques réponses courtes. Je me suis dit que vous seriez peut-être intéressé par un peu plus long.

Version longue avec code Une façon d'illustrer ce que cela pourrait être montrer comment vous implémentez Écart-type de l'échantillon une fois en utilisant foreach et une fois en utilisant .Aggregate. Note: Je n'ai pas priorisé la performance ici donc je l'ai itérée plusieurs fois sur la collection inutilement

D'abord une fonction d'aide utilisée pour créer une somme de distances quadratiques:

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

Ensuite, échantiez la déviation standard en utilisant 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));
}

Puis une fois en utilisant .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));
}

Notez que ces fonctions sont identiques à l'exception de la façon dont sumOfQuadraticDistance est calculé:

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

Contre:

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

Donc ce que .Aggregate fait est qu'il encapsule ce modèle d'agrégateur et je m'attends à ce que l'implémentation de .Aggregate ressemble à ceci:

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

L'utilisation des fonctions de déviation standard ressemblerait à ceci:

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

A mon humble avis

Alors, est-ce que .Aggregate aide la lisibilité? En général, j'adore LINQ parce que je pense que .Where, .Select, .OrderBy et ainsi de suite aide grandement la lisibilité (si vous évitez les sélections hiérarchiques inline). L'agrégat doit être dans Linq pour des raisons de complétude mais personnellement je ne suis pas si convaincu que .Aggregate ajoute de la lisibilité par rapport à un foreach bien écrit.


54
2017-08-18 10:39



Une image vaut mieux que mille mots

Rappel: Func<A, B, C> est une fonction avec deux entrées de type A et B, qui renvoie un C.

Enumerable.Aggregate a trois surcharges:


Surcharge 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


Cette surcharge est simple, mais elle a les limitations suivantes:

  • la séquence doit contenir au moins un élément,
    sinon la fonction va jeter un InvalidOperationException.
  • les éléments et le résultat doivent être du même type.



Surcharge 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


Cette surcharge est plus générale:

  • une valeur de graine doit être fournie (bIn).
  • la collection peut être vide,
    dans ce cas, la fonction donnera la valeur de départ comme résultat.
  • les éléments et le résultat peuvent avoir différents types.



Surcharge 3:

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


La troisième surcharge n'est pas très utile IMO.
La même chose peut être écrite plus succinctement en utilisant la surcharge 2 suivie d'une fonction qui transforme son résultat.


Les illustrations sont adaptées de cet excellent blog.


20
2018-04-13 12:10



L'agrégat est essentiellement utilisé pour regrouper ou synthétiser des données.

Selon MSDN            "Fonction d'agrégat Applique une fonction d'accumulateur sur une séquence."

Exemple 1: Ajouter tous les nombres dans un tableau.

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

* important: La valeur agrégée initiale par défaut est l'élément 1 dans la séquence de collecte. i.e: la valeur initiale de la variable totale sera 1 par défaut.

explication variable

total: il contiendra la somme de la valeur (valeur agrégée) renvoyée par la fonction.

nextValue: c'est la valeur suivante dans la séquence de tableau. Cette valeur est ajoutée à la valeur agrégée i.e total.

Exemple 2: Ajouter tous les éléments dans un tableau. Définissez également la valeur initiale de l'accumulateur pour commencer à ajouter à partir de 10.

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

explication des arguments:

le premier argument est la valeur initiale (valeur de départ, c'est-à-dire la valeur initiale) qui sera utilisée pour commencer l'addition avec la valeur suivante dans le tableau.

le second argument est un func qui est un func qui prend 2 int.

1.total: ceci sera le même qu'avant la somme totale (valeur agrégée) retournée par la fonction après le calcul.

2.nextValue:: c'est la prochaine valeur dans la séquence de tableau. Cette valeur est ajoutée à la valeur agrégée i.e total.

Le débogage de ce code vous permettra également de mieux comprendre le fonctionnement global.


13
2017-09-19 01:11



Appris beaucoup de Jamiec's répondre.

Si le seul besoin est de générer une chaîne CSV, vous pouvez essayer ceci.

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

Voici un test avec 1 million de chaînes

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

Le code source est ici


6
2017-09-03 20:43



En plus de toutes les bonnes réponses ici, je l'ai également utilisé pour parcourir un élément à travers une série d'étapes de transformation.

Si une transformation est implémentée en tant que Func<T,T>, vous pouvez ajouter plusieurs transformations à un List<Func<T,T>> et utilise Aggregate marcher une instance de T à travers chaque étape.

Un exemple plus concret

Vous voulez prendre un string valeur, et le parcourir à travers une série de transformations de texte qui pourraient être construits par programmation.

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

Cela créera une chaîne de transformations: Supprimer les espaces avant et arrière -> supprimer le premier caractère -> supprimer le dernier caractère -> convertir en majuscule. Les étapes de cette chaîne peuvent être ajoutées, supprimées ou réorganisées selon les besoins, pour créer le type de pipeline de transformation requis.

Le résultat final de ce pipeline spécifique est que " cat " devient "A".


Cela peut devenir très puissant une fois que vous vous rendez compte que T peut être n'importe quoi. Cela pourrait être utilisé pour les transformations d'image, comme les filtres, en utilisant BitMap par exemple;


3
2017-09-08 20:01



Une définition courte et essentielle pourrait être la suivante: La méthode d'extension Linq Aggregate permet de déclarer une sorte de fonction récursive appliquée sur les éléments d'une liste, dont les opérandes sont deux: les éléments dans l'ordre où ils sont présents dans la liste, un élément à la fois, et le résultat de l'itération récursive précédente ou rien si ce n'est encore la récursivité.

De cette manière, vous pouvez calculer la factorielle des nombres ou concaténer les chaînes.


0
2017-07-12 08:46



Ceci est une explication sur l'utilisation Aggregate sur une API Fluent telle que Linq Sorting.

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

et laisse voir que nous voulons mettre en œuvre une fonction de tri qui prend un ensemble de champs, c'est très facile à utiliser Aggregate au lieu d'une boucle for, comme ceci:

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

Et nous pouvons l'utiliser comme ceci:

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

0
2017-09-17 17:41