Explicación del algoritmo agregado de LINQ

Esto puede sonar cojo, pero no he podido encontrar una explicación realmente buena de Aggregate .

Bueno significa corto, descriptivo, completo con un pequeño y claro ejemplo.

La definición más fácil de comprender de Aggregate es que realiza una operación en cada elemento de la lista teniendo en cuenta las operaciones que han tenido lugar anteriormente. Es decir, realiza la acción en el primer y segundo elemento y lleva el resultado hacia adelante. Luego opera en el resultado anterior y el tercer elemento y continúa. etc.

Ejemplo 1. Sumar números

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

Esto agrega 1 y 2 para hacer 3 . Luego agrega 3 (resultado de anterior) y 3 (siguiente elemento en secuencia) para hacer 6 . Luego agrega 6 y 4 para hacer 10 .

Ejemplo 2. crea un csv a partir de una matriz de cadenas

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

Esto funciona de la misma manera. Concatenar a coma b para hacer a,b . Luego concatena a,b con una coma c para hacer a,b,c . y así.

Ejemplo 3. Multiplicando números usando una semilla

Para completar, hay una sobrecarga de Aggregate que toma un valor de inicialización.

 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) 

Al igual que en los ejemplos anteriores, esto comienza con un valor de 5 y lo multiplica por el primer elemento de la secuencia 10 dando un resultado de 50 . Este resultado se traslada y se multiplica por el siguiente número en la secuencia 20 para dar un resultado de 1000 . Esto continúa a través de los 2 elementos restantes de la secuencia.

Ejemplos en vivo: http://rextester.com/ZXZ64749
Documentos: http://msdn.microsoft.com/en-us/library/bb548651.aspx


Apéndice

El ejemplo 2, arriba, usa la concatenación de cadenas para crear una lista de valores separados por una coma. Esta es una forma simplista de explicar el uso de Aggregate que fue la intención de esta respuesta. Sin embargo, si se usa esta técnica para crear realmente una gran cantidad de datos separados por comas, sería más apropiado usar un StringBuilder , y esto es totalmente compatible con Aggregate usando la sobrecarga sembrada para iniciar el 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); 

Ejemplo actualizado: http://rextester.com/YZCVXV6464

En parte depende de la sobrecarga de la que está hablando, pero la idea básica es:

  • Comience con una semilla como el “valor actual”
  • Iterato sobre la secuencia. Para cada valor en la secuencia:
    • Aplicar una función especificada por el usuario para transformar (currentValue, sequenceValue) en (nextValue)
    • Establecer currentValue = nextValue
  • Devuelve el valor currentValue

Puede encontrar útil la publicación Aggregate en mi serie Edulinq : incluye una descripción más detallada (incluidas las diversas sobrecargas) e implementaciones.

Un ejemplo simple es usar Aggregate como alternativa al 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); 

O tal vez sumndo todas las longitudes de las cadenas en una secuencia de cadenas:

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

Personalmente, rara vez me resulta útil Aggregate ; los métodos de agregación “a medida” suelen ser lo suficientemente buenos para mí.

El agregado súper corto funciona como fold en Haskell / ML / F #.

Ligeramente más largo .Max (), .Min (), .Sum (), .Average () itera sobre los elementos en una secuencia y los agrega usando la función de agregado respectiva. . Aggregate () es un agregador generalizado que permite al desarrollador especificar el estado de inicio (también conocido como semilla) y la función de agregado.

Sé que solicitó una breve explicación, pero pensé que mientras otros daban un par de respuestas cortas, pensé que quizás le interesaría un poco más.

Versión larga con código Una forma de ilustrar lo que podría ser mostrar cómo implementar Desviación estándar de muestra una vez usando foreach y una vez usando .Agregar. Nota: no he priorizado el rendimiento aquí, así que repito varias veces innecesariamente sobre la recostackción

Primero una función auxiliar utilizada para crear una sum de distancias cuadráticas:

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

Luego muestre la desviación estándar usando ForEach:

 static double SampleStandardDeviation_ForEach ( this IEnumerable 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)); } 

Luego, una vez que use. Agregado:

 static double SampleStandardDeviation_Aggregate ( this IEnumerable 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)); } 

Tenga en cuenta que estas funciones son idénticas, excepto por cómo se calcula sumOfQuadraticDistance:

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

Entonces, lo que .Growgate hace es que encapsula este patrón de agregación y espero que la implementación de .Aggregate se vea más o menos así:

 public static TAggregate Aggregate ( this IEnumerable values, TAggregate seed, Func aggregator ) { var state = seed; foreach (var value in values) { state = aggregator (state, value); } return state; } 

El uso de las funciones de desviación estándar se vería así:

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

En mi humilde opinión

Entonces lo hace. ¿Ayuda a la legibilidad de la ayuda? En general, me encanta LINQ porque creo que .Where, .Select, .OrderBy, etc., ayuda en gran medida a la legibilidad (si se evitan las selecciones jerárquicas incorporadas). El agregado tiene que estar en Linq por razones de exhaustividad, pero personalmente no estoy tan convencido de que .Agregar agrega legibilidad en comparación con un foreach bien escrito.

Una imagen vale mas que mil palabras

Recordatorio: Func es una función con dos entradas de tipo A y B , que devuelve una C

Enumerable.Aggregate tiene tres sobrecargas:

Sobrecarga 1:

 A Aggregate(IEnumerable a, Func f) 

Agregado1

Ejemplo:

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

Esta sobrecarga es simple, pero tiene las siguientes limitaciones:

  • la secuencia debe contener al menos un elemento,
    de lo contrario, la función arrojará una InvalidOperationException .
  • los elementos y el resultado deben ser del mismo tipo.


Sobrecarga 2:

 B Aggregate(IEnumerable a, B bIn, Func f) 

Agregado2

Ejemplo:

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

Esta sobrecarga es más general:

  • se debe proporcionar un valor semilla ( bIn ).
  • la colección puede estar vacía,
    en este caso, la función arrojará el valor inicial como resultado.
  • los elementos y el resultado pueden tener diferentes tipos.


Sobrecarga 3:

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

La tercera sobrecarga no es muy útil IMO.
Lo mismo se puede escribir de manera más sucinta utilizando la sobrecarga 2 seguida de una función que transforma su resultado.

Las ilustraciones están adaptadas de este excelente blogpost .

El agregado se usa básicamente para agrupar o resumir datos.

De acuerdo con MSDN “Agregar función aplica una función de acumulador sobre una secuencia”.

Ejemplo 1: Agregue todos los números en una matriz.

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

* importante: el valor agregado inicial por defecto es el 1 elemento en la secuencia de recostackción. es decir: el valor inicial de la variable total será 1 por defecto.

explicación variable

total: mantendrá el valor de sum (valor agregado) devuelto por el func.

nextValue: es el siguiente valor en la secuencia de la matriz. Este valor se agrega al valor agregado, es decir, total.

Ejemplo 2: agregue todos los elementos en una matriz. Configure también el valor inicial del acumulador para comenzar a agregar desde 10.

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

explicación de argumentos:

el primer argumento es el inicial (valor inicial, es decir, el valor inicial) que se utilizará para comenzar la sum con el siguiente valor en la matriz.

el segundo argumento es un func que es un func que toma 2 int.

1.total: esto mantendrá igual que antes del valor de sum (valor agregado) devuelto por el func después del cálculo.

2.nextValue:: es el siguiente valor en la secuencia de la matriz. Este valor se agrega al valor agregado, es decir, total.

También la depuración de este código le dará una mejor comprensión de cómo funciona el agregado.

Aprendí mucho de la respuesta de Jamiec .

Si la única necesidad es generar cadena CSV, puede intentar esto.

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

Aquí hay una prueba con 1 millón de cuerdas

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

El código fuente está aquí

Además de todas las excelentes respuestas aquí, también lo he usado para recorrer un elemento a través de una serie de pasos de transformación.

Si se implementa una transformación como Func , puede agregar varias transformaciones a una List> y usar Aggregate para recorrer una instancia de T través de cada paso.

Un ejemplo más concreto

Desea tomar un valor de string y recorrer una serie de transformaciones de texto que se podrían generar programáticamente.

 var transformationPipeLine = new List>(); 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); 

Esto creará una cadena de transformaciones: elimine los espacios iniciales y finales -> eliminar el primer carácter -> eliminar el último carácter -> convertir a mayúsculas. Los pasos en esta cadena se pueden agregar, eliminar o reordenar según sea necesario para crear cualquier clase de canalización de transformación que se requiera.

El resultado final de esta tubería específica es que " cat " convierte en "A" .


Esto puede volverse muy poderoso una vez que te das cuenta de que T puede ser cualquier cosa . Esto podría usarse para transformaciones de imagen, como filtros, usando BitMap como ejemplo;

Una definición breve y esencial podría ser esta: El método de extensión de agregación de Linq permite declarar una especie de función recursiva aplicada a los elementos de una lista, cuyos operandos son dos: los elementos en el orden en que están presentes en la lista, un elemento a la vez, y el resultado de la iteración recursiva previa o nada si aún no es recurrencia.

De esta forma, puede calcular el factorial de números o concatenar cadenas.

Esta es una explicación sobre el uso de Aggregate en una API fluida, como la ordenación de Linq.

 var list = new List(); var sorted = list .OrderBy(s => s.LastName) .ThenBy(s => s.FirstName) .ThenBy(s => s.Age) .ThenBy(s => s.Grading) .ThenBy(s => s.TotalCourses); 

y veamos que queremos implementar una función de ordenamiento que tome un conjunto de campos, esto es muy fácil usando Aggregate lugar de un for-loop, como este:

 public static IOrderedEnumerable MySort( this List list, params Func[] 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)); } 

Y podemos usarlo así:

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

Agregado utilizado para sumr columnas en una matriz de enteros multidimensional

  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 rowSums = nonMagicSquare .Select(row => row.Sum()); IEnumerable colSums = nonMagicSquare .Aggregate( (priorSums, currentRow) => priorSums.Select((priorSum, index) => priorSum + currentRow[index]).ToArray() ); 

Seleccionar con índice se utiliza dentro del func Aggregate para sumr las columnas coincidentes y devolver una nueva matriz; {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 

Pero contar el número de trues en una matriz booleana es más difícil ya que el tipo acumulado (int) difiere del tipo de fuente (bool); aquí una semilla es necesaria para usar la segunda sobrecarga.

  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 rowCounts = booleanTable .Select(row => row.Select(value => value ? 1 : 0).Sum()); IEnumerable seed = new int[booleanTable.First().Length]; IEnumerable 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 

Todos han dado su explicación. Mi explicación es así.

El método agregado aplica una función a cada elemento de una colección. Por ejemplo, tengamos la colección {6, 2, 8, 3} y la función Agregar (operador +) haga (((6 + 2) +8) +3) y devuelva 19

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

En este ejemplo, se pasa el método denominado Add en lugar de la expresión lambda.

 var numbers = new List { 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; }