CodeGym /Cursos /C# SELF /Agregación avanzada con Ag...

Agregación avanzada con Aggregate

C# SELF
Nivel 32 , Lección 4
Disponible

1. Introducción

Los viejos y confiables métodos de LINQ son como la comida de la cantina: eliges "suma", "mínimo" o "media" y sigues adelante. Pero a veces quieres algo especial. Ahí es donde aparece Aggregate — como un chef que cocina según tu receta.

Si lo comparas con la programación funcional, Aggregate es como Reduce (o fold): recorres la colección y paso a paso la pliegas en un solo valor. Cómo hacerlo — lo decides tú. Control total, creatividad total.

Tareas que mola resolver con Aggregate:

  • Obtener sumas complejas: por ejemplo, el producto de todos los números, o la suma solo de los pares/impares, o la suma según una regla especial.
  • Unir cadenas con lógica personalizada (por ejemplo, usando un separador diferente para índices pares e impares).
  • Construir informes de texto ("listas Markdown", HTML, cualquier formato personalizado).
  • Construir colecciones con estado mutable (por ejemplo, crear un diccionario a partir de una lista de objetos según una regla especial de clave).
  • Cualquier cálculo complejo que no esté en la lista de funciones de agregación estándar.

2. Signatura y principio de funcionamiento del método Aggregate

Vamos a echar un vistazo a la documentación oficial de Microsoft sobre Enumerable.Aggregate:

public static TAccumulate Aggregate<TSource, TAccumulate>(
    this IEnumerable<TSource> source, 
    TAccumulate seed, 
    Func<TAccumulate, TSource, TAccumulate> func
)

Aquí va la explicación:

  • source — tu colección de origen.
  • seed — "valor inicial" (puedes verlo como el acumulador de arranque).
  • func — función que recibe dos argumentos: el valor acumulado (acc), el elemento actual de la colección y devuelve el nuevo valor acumulado.

También hay una sobrecarga sencilla:

public static TSource Aggregate<TSource>(
    this IEnumerable<TSource> source, 
    Func<TSource, TSource, TSource> func
)

En esta variante, el "inicial" es el primer elemento de la colección, y luego func se llama desde el segundo hasta el último.

3. Ejemplos más sencillos de uso de Aggregate

Empezamos con una mini-intriga para estudiantes: ¿sabías que Aggregate puede reemplazar Sum o Product?

int[] numbers = { 2, 3, 4 };

// Calcular la suma
int sum = numbers.Aggregate((acc, val) => acc + val); // acc = acumulado, val = siguiente elemento

// Calcular el producto
int product = numbers.Aggregate((acc, val) => acc * val);

Console.WriteLine(sum);     // 9
Console.WriteLine(product); // 24

Parece Sum y Multiply, ¡pero lo haces tú mismo! Puedes bromear: con el método Sum siempre puedes calcular la suma, pero con Aggregate — la suma, la "anti-suma" e incluso la "suma de raíces cuadradas".

4. Usar Aggregate para unir cadenas

Vamos a juntar todas las cadenas en una sola, separadas por coma (sin coma extra al final):

string[] words = { "C#", "LINQ", "rocks" };
string result = words.Aggregate((acc, word) => acc + ", " + word);
// resultado: "C#, LINQ, rocks"

Si la colección puede estar vacía, el valor inicial (seed) es muy útil:

// Empezamos con cadena vacía
string report = words.Aggregate(
    "Tecnologías: ",
    (acc, word) => acc + word + "; ",
    acc => acc.TrimEnd(' ', ';') // al final quitamos el ";" extra
);

Console.WriteLine(report); // "Tecnologías: C#; LINQ; rocks"

Fíjate en el tercer argumento — la función de transformación del resultado (result selector), que está en la sobrecarga con seed. Es como el "postre" del plato final.

5. Aggregate en una app en desarrollo

Vamos a recordar nuestra app de prácticas, con la que llevamos ya unos días. Supón que tienes la clase Student:

public class Student
{
    public string Name { get; set; }
    public int Grade { get; set; }
}

Lista de estudiantes:

var students = new List<Student>
{
    new Student { Name = "Alicia", Grade = 5 },
    new Student { Name = "Bob", Grade = 4 },
    new Student { Name = "Lesley", Grade = 3 },
    new Student { Name = "María", Grade = 5 }
};

Tarea: obtener una cadena tipo
"Mejores: Alicia, María"
— o sea, todos los que tienen Grade == 5.

Como suelen hacerlo los novatos:

var best = "";
foreach (var s in students)
{
    if (s.Grade == 5)
        best += s.Name + ", ";
}
best = best.TrimEnd(',', ' ');
Console.WriteLine("Mejores: " + best);

Y ahora — estilo LINQ con Aggregate:

var bestStr = students
    .Where(s => s.Grade == 5)
    .Select(s => s.Name)
    .Aggregate("Mejores: ", (acc, name) => acc + name + ", ")
    .TrimEnd(',', ' ');

Console.WriteLine(bestStr);

Lo guay: mínimo código, máxima legibilidad. Incluso si tu jefe no sabe LINQ, pillará tu idea (o al menos apreciará el esfuerzo si no la pilla).

6. Escenarios más complejos

En realidad, el acumulador en Aggregate puede ser no solo un número o cadena, sino lo que sea: un diccionario, tu propia clase o estructura.

Por ejemplo: contar cuántos estudiantes hay para cada nota:

var gradeCounts = students.Aggregate(
    new Dictionary<int, int>(),
    (dict, student) => {
        if (dict.ContainsKey(student.Grade))
            dict[student.Grade]++;
        else
            dict[student.Grade] = 1;
        return dict;
    }
);

// Para mostrar:
foreach (var pair in gradeCounts)
{
    Console.WriteLine($"Nota {pair.Key}: {pair.Value} estudiantes");
}

Este enfoque básicamente implementa GroupBy, pero a mano. ¿Por qué no usar simplemente GroupBy? A veces necesitas una agregación especial que no está en los métodos estándar de LINQ, como sumar solo si el estudiante no es Lesley, o construir un informe para otro informe.

7. Visualización: cómo funciona Aggregate (diagrama de bloques)

Supón que tienes el array { 2, 4, 3 } y quieres acumular la suma:


acc: 2 (primer elemento)
      |
      v
val: 4
acc = acc + val = 2 + 4 = 6
      |
      v
val: 3
acc = acc + val = 6 + 3 = 9
      |
      v
[Todos los elementos procesados]
      |
      v
Resultado: 9

Esquema similar para la sobrecarga con seed:


seed: 0
      |
      v
val: 2
acc = 0 + 2 = 2
      |
      v
val: 4
acc = 2 + 4 = 6
      |
      v
val: 3
acc = 6 + 3 = 9
      |
      v
Resultado: 9

Comparación de Aggregate y otros métodos de agregación

Método Comportamiento estándar Flexibilidad Para colecciones vacías Ejemplo de uso
Sum
Suma números Baja Devuelve 0
[1,2,3].Sum() = 6
Count
Cuenta elementos Baja Devuelve 0
[a,b].Count() = 2
Aggregate
Cualquier cálculo Alta Requiere seed
[1,2,3].Aggregate((a,b)=>a*b)=6
string.Join
Une cadenas Media Para vacías = ""
string.Join(", ", arr)

8. Consejos prácticos, errores típicos y puntos importantes

Por su flexibilidad, Aggregate puede dar sorpresas si lo usas mal. Los errores más comunes:

A veces los programadores se olvidan del seed (valor inicial) y no tienen en cuenta que si la colección está vacía, la sobrecarga sin seed lanza una excepción (InvalidOperationException). Así que para colecciones vacías usa la sobrecarga con seed:

var sum = new int[0].Aggregate(0, (acc, n) => acc + n); // ¡Funciona! Devuelve 0

Si acumulas una cadena con Aggregate, es fácil que te salga un separador extra (por ejemplo, "," al final). Mejor quítalo con .TrimEnd(',', ' ') — o usa string.Join si solo quieres unir cadenas.

El acumulador de tipo mutable (por ejemplo, List o Dictionary) se usa mucho en Aggregate, pero ojo: si lo modificas in-place, en cada paso todas las referencias apuntan al mismo objeto. Esto puede dar efectos raros en operaciones paralelas o si esperas copias. Así que en estilo funcional puro, mejor devuelve un objeto nuevo en cada paso, no modifiques el anterior.

En código que usan otros, no compliques Aggregate solo por "fardar": a los novatos les parece más difícil que un simple foreach o los agregados estándar. Si la tarea es básica — usa Sum, Count, Join. Pero si necesitas "montar texto con una plantilla especial" — Aggregate es tu colega.

9. Relación con el trabajo real, entrevistas y frameworks

En la industria, el método Aggregate aparece mucho donde se necesitan cálculos o plegados de datos poco habituales, por ejemplo, al crear informes complejos, estadísticas, grafos, calcular hashes, generar ids únicos o incluso para construir componentes de UI a partir de colecciones con lógica especial.

En entrevistas, casi siempre caen preguntas de LINQ tipo: "¿Cómo obtienes la suma de los elementos de un array?", "¿Cómo conviertes una lista de cadenas en una sola cadena?" o "¿Cómo cuentas la cantidad de elementos únicos?" — y suelen esperar soluciones usando LINQ y también Aggregate. Las preguntas pueden ser creativas: "¿Puedes con LINQ calcular la suma de los cuadrados de los números pares y luego devolver una cadena que describa el proceso?"

En muchas librerías y frameworks populares de .NET, como Entity Framework, Dapper, RavenDB, el método Aggregate rara vez se usa directamente en la base de datos, pero a nivel de código es muy útil para agregaciones en memoria.

2
Tarea
C# SELF, nivel 32, lección 4
Bloqueada
Agregación más simple — producto de un array
Agregación más simple — producto de un array
1
Cuestionario/control
Agrupación de datos, nivel 32, lección 4
No disponible
Agrupación de datos
LINQ avanzado: agrupación y funciones agregadas
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION