web-dev-qa-db-fra.com

L'algorithme LINQ Aggregate expliqué

Cela peut paraître nul, mais je n’ai pas trouvé une très bonne explication de Aggregate.

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

661
Alexander Beletsky

La définition la plus simple à comprendre de Aggregate consiste à effectuer une opération sur chaque élément de la liste en tenant compte des opérations précédentes. C'est-à-dire qu'il exécute l'action sur les premier et deuxième éléments et reporte le résultat. Ensuite, il opère sur le résultat précédent et le troisième élément et continue. etc.

Exemple 1. Totalisation des nombres

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

Ceci ajoute 1 et 2 pour créer 3. Ensuite, ajoute 3 (résultat du précédent) et 3 (élément suivant de la séquence) pour obtenir 6. Ensuite, ajoute 6 et 4 pour créer 10.

Exemple 2. créez 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 à peu près de la même manière. Concaténer a une virgule et b pour créer a,b. Concatène ensuite a,b avec une virgule et c pour créer a,b,c. etc.

Exemple 3. Multiplier des nombres à l'aide d'une graine

Pour être complet, il existe un surcharge sur Aggregate qui prend une valeur de départ.

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)

Comme dans les exemples ci-dessus, ceci commence par une valeur de 5 et le multiplie par le premier élément de la séquence 10, ce qui donne 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


Addendum

L'exemple 2 ci-dessus utilise la concaténation de chaînes pour créer une liste de valeurs séparées par une virgule. C’est une façon simpliste d’expliquer l’utilisation de Aggregate qui était l’intention de cette réponse. Toutefois, si vous utilisez cette technique pour créer une grande quantité de données séparées par des virgules, il serait plus approprié d’utiliser un StringBuilder, ce qui est tout à fait compatible avec Aggregate qui utilise la surcharge parsemée pour initialiser 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

953
Jamiec

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

  • Commencez avec une valeur initiale 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) en (nextValue)
    • Définir currentValue = nextValue
  • Retourne le currentValue final

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

Un exemple simple consiste à utiliser Aggregate comme 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 en sommant 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 trouve rarement Aggregate utile - les méthodes d'agrégation "sur mesure" me suffisent généralement.

126
Jon Skeet

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

Légèrement plus long .Max (), .Min (), .Sum (), .Average (): toutes les itérations sur les éléments d'une séquence et leur agrégation en utilisant la fonction d'agrégation 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 (ou graine) et la fonction d’agrégation.

Je sais que vous avez demandé une brève explication, mais je me suis dit que les autres donnaient quelques réponses courtes, je pensais que vous seriez peut-être intéressé par une réponse un peu plus longue

Version longue avec code Une façon d’illustrer ce que cela pourrait être de montrer comment vous implémentez Exemple de déviation standard une fois en utilisant foreach et une fois en utilisant .Aggregate. Remarque: je n'ai pas hiérarchisé les performances ici, donc j'itère plusieurs fois inutilement sur la collection

D'abord une fonction d'assistance 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;
}

Puis échantillonnez l'écart type à l'aide de 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, sauf pour le mode de calcul de sumOfQuadraticDistance:

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 fait .Aggregate, il encapsule ce modèle d'agrégateur et je m'attends à ce que l'implémentation de .Aggregate ressemble à quelque chose comme ça:

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

Utiliser les fonctions d’écart-type 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);

IMHO

Alors, est-ce que .Aggregate améliore la lisibilité? En général, j'aime LINQ parce que je pense que .Where, .Select, .OrderBy, etc., améliorent grandement la lisibilité (si vous évitez les scripts hiérarchiques en ligne.). L'agrégat doit être dans Linq pour des raisons d'exhaustivité, mais personnellement, je ne suis pas convaincu que .Aggregate améliore la lisibilité par rapport à un foreach bien écrit.

Une image vaut mieux que mille mots

Rappel: Func<A, B, C> est une fonction à deux entrées de type A et B, renvoyant 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 présente les limitations suivantes:

  • la séquence doit contenir au moins un élément,
    sinon la fonction va lancer 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 départ 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 à l'OMI.
On peut écrire de manière plus succincte la même chose en utilisant la surcharge 2 suivie d’une fonction qui transforme son résultat.


Les illustrations sont adaptées de cet excellent article de blog .

31
3dGrabber

L'agrégation est essentiellement utilisée pour grouper ou résumer des données.

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

Exemple 1: Ajoutez tous les nombres d'un tableau.

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

* important: la valeur d'agrégation initiale par défaut est l'élément 1 dans la séquence de collecte. c'est-à-dire que la valeur initiale totale de la variable sera 1 par défaut.

explication variable

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

nextValue: c'est la valeur suivante de la séquence du tableau. Cette valeur est ensuite ajoutée à la valeur agrégée, c'est-à-dire au total.

Exemple 2: Ajouter tous les éléments d'un tableau. Définissez également la valeur initiale de l’accumulateur pour commencer l’ajout 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 l'initiale (valeur initiale, c'est-à-dire, valeur initiale) qui sera utilisée pour commencer l'addition avec la valeur suivante du tableau.

le second argument est une fonction qui est une fonction qui prend 2 int.

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

2.nextValue:: c'est la valeur suivante de la séquence du tableau. Cette valeur est ensuite ajoutée à la valeur agrégée, c'est-à-dire au total.

Le débogage de ce code vous permettra également de mieux comprendre le fonctionnement des agrégats.

14
maxspan

J'ai beaucoup appris de Jamiec's answer.

Si le seul besoin est de générer une chaîne CSV, essayez 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

7
Rm558

En plus de toutes les bonnes réponses ici déjà, 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 utiliser Aggregate pour parcourir une instance de T à chaque étape.

Un exemple plus concret

Vous voulez prendre une valeur string et l'expliquer à travers une série de transformations de texte pouvant être générées par programme.

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: Supprimez les espaces de début et de fin -> supprimez le premier caractère -> supprimez le dernier caractère -> convertissez-les en majuscules. 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 réalisez que T peut être n'importe quoi. Cela pourrait être utilisé pour les transformations d'image, comme les filtres, en utilisant BitMap comme exemple;

5
Bradley Uffner

Voici une définition brève et essentielle: La méthode d’extension Linq Aggregate permet de déclarer une sorte de fonction récursive appliquée aux éléments d’une liste, dont les opérandes sont deux: les éléments dans l’ordre dans lequel ils se trouvent dans la liste, un élément à la fois, et le résultat de la précédente itération récursive ou rien sinon la récursion.

De cette façon, vous pouvez calculer la factorielle des nombres ou concaténer des chaînes.

0
Ciro Corvino

Tout le monde a donné son explication. Mon explication est comme ça.

La méthode d'agrégation applique une fonction à chaque élément d'une collection. Par exemple, regroupons {6, 2, 8, 3} et la fonction Ajouter (opérateur +) comme il le fait (((6 + 2) +8) +3) et renvoie 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

Dans cet exemple, il est passé la méthode nommée Add au lieu de l'expression lambda.

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

Ceci est une explication sur l'utilisation de 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 voyons si nous voulons implémenter une fonction de tri qui prend un ensemble de champs, ceci est très facile en utilisant 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 on peut l'utiliser comme ça:

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

Agrégat utilisé pour additionner les colonnes dans un tableau entier multidimensionnel

        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 est utilisé dans la fonction Aggregate pour additionner les colonnes correspondantes et renvoyer un nouveau tableau; {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

Mais compter le nombre de vrais dans un tableau booléen est plus difficile car le type accumulé (int) diffère du type source (bool); ici, une graine est nécessaire pour utiliser la seconde surcharge.

        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