Explication de l'algorithme LINQ Aggregate

723

Cela peut sembler boiteux, mais je n'ai pas pu trouver une très bonne explication Aggregate.

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

Alexander Beletsky
la source

Réponses:

1015

La définition la plus simple à comprendre de Aggregateest qu'il effectue 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 le premier et le deuxième élément et fait avancer 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. Additionner 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)

Cela ajoute 1et 2à faire 3. Ajoute ensuite 3(résultat du précédent) et 3(élément suivant dans la séquence) à faire 6. Ajoute ensuite 6et 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. Concaténer aune virgule et bfaire a,b. Concatène ensuite a,b avec une virgule et cà faire a,b,c. etc.

Exemple 3. Multiplication de nombres à l'aide d'une graine

Pour être complet, il y a une surcharge de ce Aggregatequi 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)

Tout comme les exemples ci-dessus, cela commence par une valeur de 5et la multiplie par le premier élément de la séquence 10donnant un résultat de 50. Ce résultat est reporté et multiplié par le nombre suivant de la séquence 20pour 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înes pour créer une liste de valeurs séparées par une virgule. Il s'agit d'une manière simpliste d'expliquer l'utilisation de Aggregatelaquelle é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 cela est entièrement compatible avec l' Aggregateutilisation de la surcharge prédéfinie pour lancer 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

Jamiec
la source
11
Une autre explication de la première description est que la fonction que vous fournissez combine toujours les deux premiers membres jusqu'à ce que le tableau soit réduit à un élément. Ainsi en [1,2,3,4]sera-t-il [3,3,4]alors [6,4]et enfin [10]. Mais au lieu de renvoyer un tableau d'une seule valeur, vous obtenez simplement la valeur elle-même.
David Raab
2
Puis-je interrompre / quitter rapidement une fonction d'agrégation? Par exemple, chars.Aggregate ((a, b) => {if (a == 'a') casse tout l'agrégat sinon retourne a + ',' + b})
Jeff Tian
13
@JeffTian - Je suggérerais d'enchaîner un TakeWhilepuis un Aggregate- c'est le devoir des extensions Enumerable - ils sont facilement chaînables. Vous vous retrouvez donc avec TakeWhile(a => a == 'a').Aggregate(....). Voir cet exemple: rextester.com/WPRA60543
Jamiec
2
En tant que sidenote sur l'addendum, le bloc entier pourrait facilement être remplacé par var csv = string.Join(",", chars)(pas besoin d'agrégat ou de constructeurs de chaînes) - mais oui, je sais que l'objectif de la réponse était de donner un exemple d'utilisation d'agrégat, donc c'est cool. Mais je voulais quand même mentionner qu'il n'est pas recommandé de simplement joindre des chaînes, il y a déjà une méthode dédiée à cela ....
T_D
2
un autre usage courant (jusqu'à présent le seul que j'ai vu dans le code de production) est d'obtenir des éléments min ou max commevar biggestAccount = Accounts.Aggregate((a1, a2) => a1.Amount >= a2.Amount ? a1 : a2);
Franck
133

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

  • Commencez avec une graine comme "valeur actuelle"
  • Répétez la séquence. Pour chaque valeur de la séquence:
    • Appliquer une fonction spécifiée par l'utilisateur à transformer (currentValue, sequenceValue)en(nextValue)
    • Ensemble currentValue = nextValue
  • Retourner la finale currentValue

Vous pouvez trouver le Aggregatemessage 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 Aggregatecomme 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 additionnant 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 rarementAggregate utile - les méthodes d'agrégation "sur mesure" sont généralement assez bonnes pour moi.

Jon Skeet
la source
6
@Jon Existe-t-il des variantes asynchrones d'Agrégat qui divisent les éléments en une arborescence afin que le travail puisse être divisé entre les cœurs? Il semble que la conception de la méthode soit cohérente avec les concepts de "réduire" ou de "plier", mais je ne sais pas si elle le fait vraiment sous le capot, ou simplement en répétant la liste des éléments.
AaronLS
@Jon: l'edulink mentionné ci-dessus ne fonctionne pas, pouvez-vous me rediriger vers le bon lien. Et pouvez-vous s'il vous plaît être plus précis sur le terme de fonctions d'agrégation "sur mesure" que vous avez utilisé dans votre réponse.
Koushik
1
@Koushik: J'ai corrigé le lien dans le message. Par fonctions d'agrégation "sur mesure", j'entends des choses comme Max / Min / Count / Sum.
Jon Skeet du
62

L' agrégat super court fonctionne comme le pli en Haskell / ML / F #.

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

Je sais que vous avez demandé une brève explication, mais je me suis dit que d'autres ont donné quelques brèves réponses, je me suis dit 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 l' exemple d'écart-type une fois en utilisant foreach et une fois en utilisant .Aggregate. Remarque: Je n'ai pas priorisé les performances ici, donc je répète inutilement plusieurs fois la collection

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

Échantillonnez ensuite 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 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)
        );

Ce que fait .Aggregate, c'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 d'écart 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

Est-ce que .Aggregate aide à la lisibilité? En général, j'aime LINQ parce que je pense que .Where, .Select, .OrderBy et ainsi de suite aident grandement la lisibilité (si vous évitez les .Selects hiérarchiques en ligne). L'agrégat doit être dans Linq pour des raisons d'exhaustivité, mais personnellement, je ne suis pas convaincu que l'agrégat ajoute de la lisibilité par rapport à un foreach bien écrit.

Juste un autre métaprogrammeur
la source
+1 Excellent! Mais les méthodes d'extension SampleStandardDeviation_Aggregate()et SampleStandardDeviation_ForEach()ne peuvent pas l'être private(par défaut en l'absence d'un qualificatif d'accès), elles auraient donc dû être accumulées par publicou internal, il me semble
Fulproof
FYI: Si je me souviens bien, les méthodes d'extension dans mon échantillon faisaient partie de la même classe qui les utilisait ==> travaux privés dans ce cas.
Juste un autre métaprogrammeur le
39

Une image vaut mieux que mille mots

Rappel:
Func<X, Y, R>est une fonction avec deux entrées de type Xet Y, qui renvoie un résultat de type R.

Enumerable.Aggregate a trois surcharges:


Surcharge 1:

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

Agrégat1

Exemple:

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


Cette surcharge est simple, mais elle présente les limitations suivantes:

  • la séquence doit contenir au moins un élément,
    sinon la fonction lancera 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)

Agrégat2

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 IMO.
La même chose peut être écrite de manière plus succincte 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 .

3dGrabber
la source
Ce serait une excellente réponse ... à une question sur Haskel. Mais il n'y a pas de surcharge de Aggegate.net qui prend un Func<T, T, T>.
Jamiec
4
Oui . Vous l'utilisez dans votre propre réponse!
3dGrabber le
1
Vote ascendant parce que vous décrivez soigneusement ce qui se passe lorsque la séquence est vide. Soit N le nombre d'éléments dans la source. On observe que la surcharge qui ne prend pas a seed, applique la fonction d'accumulateur N -1 fois; tandis que les autres (qui les surcharges ne prennent a seed) appliquent la fonction de l' accumulateur N fois.
Jeppe Stig Nielsen
17

L'agrégat est essentiellement utilisé pour regrouper ou résumer les données.

Selon MSDN "Aggregate Function Applique une fonction d'accumulateur sur une séquence."

Exemple 1: ajoutez 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. c'est-à-dire que la valeur initiale totale de la variable sera 1 par défaut.

explication variable

total: il contiendra la valeur de synthèse (valeur agrégée) renvoyée par le func.

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

Exemple 2: ajoutez tous les éléments d'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 l'initiale (valeur de départ, c'est-à-dire la valeur de départ) qui sera utilisée pour commencer l'addition avec la valeur suivante dans le tableau.

le deuxième argument est un func qui est un func qui prend 2 int.

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

2.nextValue:: il s'agit de la valeur suivante dans 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.

maxspan
la source
7

J'ai beaucoup appris de la réponse de Jamiec .

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 cordes

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

Le code source est ici

Rm558
la source
Lorsque j'ai exécuté le même code dans dotnetfiddle.net que celui fourni dans le lien, j'ai obtenu "Erreur fatale: la limite d'utilisation de la mémoire a été dépassée" pour "string.Join" mais Aggregate a toujours fonctionné comme prévu. Je pense donc que ce n'est pas recommandé d'utiliser String.Join
Manish Jain
Étrange? Quand j'ai commenté le premier chronomètre qui était pour Aggregate; alors je ne reçois aucun "Erreur fatale: la limite d'utilisation de la mémoire a été dépassée". S'il vous plaît, expliquez! Lien: dotnetfiddle.net/6YyumS
Manish Jain
dotnetfiddle.net a une limite de mémoire, lorsqu'il atteint l'arrêt de l'exécution. si vous déplacez le code d'agrégat avant le code String.Join, vous pouvez obtenir une erreur d'agrégat.
Rm558
7

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 à a List<Func<T,T>>et utiliser Aggregatepour parcourir une instance de Tchaque étape.

Un exemple plus concret

Vous voulez prendre une stringvaleur et la parcourir à travers une série de transformations de texte qui pourraient être construites 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: Supprimer les espaces de début et de fin -> supprimer le premier caractère -> supprimer le dernier caractère -> convertir 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, c'est qu'il " cat "devient "A".


Cela peut devenir très puissant une fois que vous réalisez que cela Tpeut être n'importe quoi . Cela pourrait être utilisé pour les transformations d'images, comme les filtres, en utilisant BitMapcomme exemple;

Bradley Uffner
la source
4

Définition

La méthode d'agrégation est une méthode d'extension pour les collections génériques. La méthode d'agrégation applique une fonction à chaque élément d'une collection. Non seulement applique une fonction, mais prend son résultat comme valeur initiale pour la prochaine itération. Ainsi, en conséquence, nous obtiendrons une valeur calculée (min, max, moyenne ou autre valeur statistique) à partir d'une collection.

Par conséquent, la méthode Aggregate est une forme d'implémentation sûre d'une fonction récursive.

Sûr , car la récursion va itérer sur chaque élément d'une collection et nous ne pouvons pas obtenir de suspension de boucle infinie par une condition de sortie incorrecte. Récursif , car le résultat de la fonction actuelle est utilisé comme paramètre pour le prochain appel de fonction.

Syntaxe:

collection.Aggregate(seed, func, resultSelector);
  • seed - valeur initiale par défaut;
  • func - notre fonction récursive. Il peut s'agir d'une expression lambda, d'un délégué Func ou d'un type de fonction TF (résultat T, T nextValue);
  • resultSelector - ce peut être une fonction comme func ou une expression pour calculer, transformer, changer, convertir le résultat final.

Comment ça fonctionne:

var nums = new[]{1, 2};
var result = nums.Aggregate(1, (result, n) => result + n); //result = (1 + 1) + 2 = 4
var result2 = nums.Aggregate(0, (result, n) => result + n, response => (decimal)response/2.0); //result2 = ((0 + 1) + 2)*1.0/2.0 = 3*1.0/2.0 = 3.0/2.0 = 1.5

Utilisation pratique:

  1. Trouvez Factorial à partir d'un nombre n:

int n = 7;
var numbers = Enumerable.Range(1, n);
var factorial = numbers.Aggregate((result, x) => result * x);

qui fait la même chose que cette fonction:

public static int Factorial(int n)
{
   if (n < 1) return 1;

   return n * Factorial(n - 1);
}
  1. Aggregate () est l'une des méthodes d'extension LINQ les plus puissantes, comme Select () et Where (). Nous pouvons l'utiliser pour remplacer Sum (), Min (). Fonctionnalité Max (), Avg (), ou pour la modifier en implémentant un contexte d'ajout:
    var numbers = new[]{3, 2, 6, 4, 9, 5, 7};
    var avg = numbers.Aggregate(0.0, (result, x) => result + x, response => (double)response/(double)numbers.Count());
    var min = numbers.Aggregate((result, x) => (result < x)? result: x);
  1. Utilisation plus complexe des méthodes d'extension:
    var path = @“c:\path-to-folder”;

    string[] txtFiles = Directory.GetFiles(path).Where(f => f.EndsWith(“.txt”)).ToArray<string>();
    var output = txtFiles.Select(f => File.ReadAllText(f, Encoding.Default)).Aggregate<string>((result, content) => result + content);

    File.WriteAllText(path + summary.txt”, output, Encoding.Default);

    Console.WriteLine(“Text files merged into: {0}”, output); //or other log info
YuriUn
la source
Très bonne première réponse. Bien joué! Dommage que ce soit une si vieille question ou vous auriez eu beaucoup de votes positifs
Jamiec
1

Il s'agit d'une explication sur l'utilisation Aggregatesur 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 que nous voulons implémenter une fonction de tri qui prend un ensemble de champs, c'est très facile à utiliser Aggregateau 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);
Jaider
la source
1

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, ayons la collection {6, 2, 8, 3} et la fonction Add (opérateur +) qu'elle 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, la méthode nommée Add est passée 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; }
bizimunda
la source
0

Une définition courte et essentielle pourrait être celle-ci: 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 dans lequel 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 pas encore la récursivité.

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

Ciro Corvino
la source
0

Agrégat utilisé pour additionner des 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 deuxième 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
Dan M
la source