Pregunta 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.


574
2017-08-18 09:51


origen


Respuestas:


La definición más fácil de entender de Aggregate es que realiza una operación en cada elemento de la lista teniendo en cuenta las operaciones anteriores. 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 la 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 una coma y b para hacer a,b. Luego se concatena a,b  con una coma y c para hacer a,b,c. y así.

Ejemplo 3. Multiplicando números usando una semilla

Para completar, hay un sobrecarga de Aggregate que toma un valor de semilla.

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 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 cuál fue la intención de esta respuesta. Sin embargo, si se utiliza 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


849
2017-08-18 09:59



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) dentro (nextValue)
    • Conjunto currentValue = nextValue
  • Devuelve el final currentValue

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

Un ejemplo simple es usar Aggregate como alternativa a 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 sumando todas las longitudes de las cadenas en una secuencia de cadenas:

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

Personalmente yo raramente encontrar Aggregate útil: los métodos de agregación "a medida" suelen ser lo suficientemente buenos para mí.


114
2017-08-18 09:54



Súper corto  El agregado 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 la muestra una vez que usa foreach y una vez que usa .Agregado. Nota: no he priorizado el rendimiento aquí, así que repito varias veces innecesariamente sobre la recopilación

Primero una función auxiliar utilizada para crear una suma 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<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));
}

Luego, una vez que use. Agregado:

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

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

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.


54
2017-08-18 10:39



Una imagen vale mas que mil palabras

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

Enumerable.Aggregate tiene tres sobrecargas:


Sobrecarga 1:

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

Aggregate1

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á un InvalidOperationException.
  • los elementos y el resultado deben ser del mismo tipo.



Sobrecarga 2:

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

Aggregate2

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 de 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<A,B,C>(IEnumerable<A> a, B bIn, Func<B,A,B> f, Func<B,C> 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.


20
2018-04-13 12:10



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

De acuerdo con MSDN            "Función agregada 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 recopilación. es decir: el valor inicial variable total será 1 por defecto.

explicación variable

total: mantendrá el valor de suma (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 valor inicial (valor inicial, es decir, valor inicial) que se utilizará para comenzar la suma 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 suma (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.


13
2017-09-19 01:11



Aprendí mucho de Jamiec responder.

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 es aquí


6
2017-09-03 20:43



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 una transformación se implementa como una Func<T,T>, puedes agregar varias transformaciones a un List<Func<T,T>> y use Aggregate caminar una instancia de T a través de cada paso.

Un ejemplo más concreto

Quieres tomar un string valor, y recorrerlo a través de una serie de transformaciones de texto que podrían construirse programáticamente.

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

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 " se convierte "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;


3
2017-09-08 20:01



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.


0
2017-07-12 08:46



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

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

y veamos que queremos implementar una función de clasificación que tome un conjunto de campos, esto es muy fácil de usar Aggregate en lugar de un bucle for, así:

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

Y podemos usarlo así:

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



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

Seleccionar con índice se utiliza dentro del func Aggregate para sumar 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<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
2018-02-28 20:16



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<int> { 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<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
2017-11-16 13:49