Los
Aggregate
Operators, nos permiten realizar operaciones matemáticas de una
temática simple sobre los elementos que forman una colección. Normalmente el
resultado de estos, es un valor numérico, aunque no siempre es obligatorio.
La ejecución de todos estos operadores es inmediata, y se
realiza justo en el momento de realizar la llamada, por lo que ninguno de ellos
tiene ejecución
diferida
o perezosa.
En este conjunto de operadores, se diferencian tres tipos.
Un primer tipo muy simple, compuesto por Count
y LongCount. Un segundo grupo también
bastante sencillo compuesto por Max,
Min, Sum y Average
y un tercero compuesto por el operador Aggregate,
más complejo y potente. El primer grupo, posee sobrecargas sin parámetros, para
secuencias numéricas sencillas y sobrecargas con filtrado (parámetro Func<T, bool>), que purifican
la secuencia antes de aplicar el cálculo. Los métodos correspondientes al
segundo grupo, poseen un número bastante considerable de sobrecargas, que van
en concordancia con el tipo de datos sobre el que se aplica el cálculo,
normalmente int, int?, double, doublé?,
decimal, decimal?, long
y long? En afinidad con su delegado genérico
Func<T, int>, Func<T, int?>, Func<T, double> …
Los operadores de este tipo, tales como: Sum, Average, Min,
Max, etc., se podrían realizar
perfectamente con el operador Aggregate,
pero parece ser que el equipo de LinQ,
los incluyó por razones de facilidad de lectura y compresión de código. Esto lo
demostraremos un poco más a fondo al final de este post.
Recuerda que aquí tienes el indice de todos los posts del Curso de LinQ.
Count y LongCount
Los métodos extensores, Count
y LongCount, retornan el número de
elementos que contiene una secuencia. La diferencia entre ellos, es que Count se utiliza para colecciones de
elementos estándar y LongCount,
para colecciones de un número de elementos mucho más elevado. El operador Count devuelve un objeto de tipo int (Int32), por lo que no podrá ser utilizado por secuencias que
contengan más de 2.147.483.647
elementos, para colecciones mayores tendremos que emplear el LongCont que devuelve un objeto de
tipo Long (Int64) y que permitirá contabilizar
elementos para colecciones de hasta 9.223.372.036.854.775.807. Ahí
queda eso.
Vamos con
sus firmas:
public static int Count<TSource>(this IEnumerable<TSource> source);
public static int Count<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
public static long LongCount<TSource>(this IEnumerable<TSource> source);
public static long LongCount<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
Ambos tienen 2 sobrecargas
cada uno, la segunda, admite un predicate,
por si queremos realizar el recuento, no de la secuencia completa, sino de un
grupo filtrado. Este predicate,
indicará las condiciones del filtrado.
Vamos a ver unos ejemplos:
static void Main(string[] args)
{
var elementos = new List<Elemento>()
{
new Elemento{ ID = 0, Amount = 10000},
new Elemento{ ID = 1, Amount = 1000},
new Elemento{ ID = 2, Amount = 2000},
new Elemento{ ID = 3, Amount = 3000}
};
var numCorto = elementos.Count();
var numLargo = elementos.LongCount();
var numCortoFiltrado = elementos.Count (e => e.Amount >= 2000);
var numLargoFiltrado = elementos.LongCount(e => e.Amount >= 2000);
Console.WriteLine($"{nameof(numCortoFiltrado)} es de tipo {numCortoFiltrado.GetType().Name}");
Console.WriteLine($"{nameof(numLargoFiltrado)} es de tipo {numLargoFiltrado.GetType().Name}");
Console.Read();
}
El resultado es exactamente el mismo, solo cambiará el tipo
de dato devuelto. El operador LongCount,
solo lo utilizaremos cuando sepamos que las secuencias son extremadamente
grandes. Normalmente, en un trabajo diario, no suele emplearse.
NOTA
No debemos confundir el método extensor
Count() de la interfaz
IEnumerable<T>,
con la propiedad
Count de Interfaz
ICollection<T> e
IList<T>. La segunda ya
existía antes de la aparición de
LinQ,
y está solo se puede aplicar sobre colecciones que implementen esta interfaz.
El método extensor
Count()
tiene un abanico mucho más amplio de uso, ya que es mayor el número de
colecciones en el
Framework que
implementan
IEnumerable<T> que
ICollection<T>.
Cuando utilizamos este operador, si lo aplicamos sobre secuencias de los tipos
ICollection<T> e
IList<T>, estos simplemente
consultarán su propiedad
Count,
haciendo la ejecución prácticamente inmediata.
Sum
El operador sum, devuelve la suma de una secuencia de
elementos. Estos elementos pueden ser nulos.
Tiene 20 sobrecargas,
las cuales están divididas en 2 grupos bien diferenciados, y que en el momento
de escribir el código, nos puede parecer que solo tiene 2, una por cada grupo.
El primero, formado por 10 sobrecargas (con un solo parámetro), que se aplican sobre una
secuencia de objetos del mismo tipo (siempre numérico) que devuelve:
public static long Sum(this IEnumerable<long> source);
public static long? Sum(this IEnumerable<long?> source);
public static float? Sum(this IEnumerable<float?> source);
public static double Sum(this IEnumerable<double> source);
public static double? Sum(this IEnumerable<double?> source);
public static decimal? Sum(this IEnumerable<decimal?> source);
public static decimal Sum(this IEnumerable<decimal> source);
public static float Sum(this IEnumerable<float> source);
public static int? Sum(this IEnumerable<int?> source);
public static int Sum(this IEnumerable<int> source);
Dicho con otras palabras, este operador se aplicará siempre
sobre colecciones de elementos numéricos y devolverá la suma de los mismos en
un objeto del mismo tipo numérico que el referente a cualquier elemento del
campo de la colección.
Unos ejemplitos:
static void Main(string[] args)
{
var elementos = new List<Elemento>()
{
new Elemento{ ID = 0, Amount = 10000},
new Elemento{ ID = 1, Amount = 1000},
new Elemento{ ID = 2, Amount = 2000},
new Elemento{ ID = 3, Amount = 3000}
};
int[] cifras = new int[] { 1000, 2000, 3000 };
int resultadoCifras = cifras.Sum();
var sumAmount = elementos.Select(a => a.Amount).Sum();
Console.WriteLine($"{nameof(resultadoCifras)} = {resultadoCifras}");
Console.WriteLine($"{nameof(sumAmount)} = {sumAmount}" );
Console.Read();
}
Como se puede apreciar las llamadas no llevan ningún parámetro.
El Segundo grupo, también está formado por 10
sobrecargas, en este caso son
métodos
genéricos,
con 2 parámetros cada una, que contienen un segundo parámetro selector, donde
le indicaremos al método el campo o la propiedad de nuestra clase de la que
queremos servirnos para realizar la suma de elementos:
public static float? Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, float?> selector);
public static double? Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, double?> selector);
public static decimal Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, decimal> selector);
public static decimal? Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, decimal?> selector);
public static double Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, double> selector);
public static int? Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, int?> selector);
public static long? Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, long?> selector);
public static float Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, float> selector);
public static long Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, long> selector);
public static int Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector);
Vamos a plasmarlo en un ejemplo;
static void Main(string[] args)
{
var elementos = new List<Elemento>()
{
new Elemento { ID = 0, Amount = 10000},
new Elemento { ID = 1, Amount = 1000},
new Elemento { ID = 2, Amount = 2000},
new Elemento { ID = 3, Amount = 3000}
};
var sumAmount = elementos.Sum(a => a.Amount);
Console.WriteLine($"{nameof(sumAmount)} = {sumAmount}" );
Console.Read();
}
Como se puede distinguir, añadimos un parámetro de tipo Func<Elemento, int>, en el que
le indicamos el campo que queremos que emplee para realizar la suma.
Si enfrentamos las 2 opciones:
var sumAmount = elementos.Select(a => a.Amount).Sum();
var sumAmount = elementos.Sum(a => a.Amount);
Vemos que en la primera, nosotros hacemos el trabajo de
selección con un
delegado
del mismo tipo, mientras que en la segunda, al recibir ya esté parámetro él
hace el trabajo por nosotros.
En caso de que la columna con la que operar sea de tipo Nullable y ésta esté compuesta por
valores nulos, el resultado será 0 por cada valor null que se encuentre:
static void Main(string[] args)
{
var elementos = new List<Elemento>()
{
new Elemento { ID = 0, Amount = null},
new Elemento { ID = 1, Amount = null},
new Elemento { ID = 2, Amount = null},
new Elemento { ID = 3, Amount = null}
};
var sumAmount = elementos.Sum(a => a.Amount);
Console.WriteLine($"{nameof(sumAmount)} = {sumAmount}" );
Console.Read();
}
Min y Max
El operador Min
y Max, tienen las mismas
peculiaridades que Sum,
pero evidentemente cumpliendo el cometido de sus respectivos nombres, buscar
los valores máximos y mínimos dentro de una secuencia.
Empezaremos con las firmas de sus métodos extensores:
public static double? Max(this IEnumerable<double?> source);
public static double Max(this IEnumerable<double> source);
public static long? Max(this IEnumerable<long?> source);
public static long Max(this IEnumerable<long> source);
public static int? Max(this IEnumerable<int?> source);
public static float Max(this IEnumerable<float> source);
public static float? Max(this IEnumerable<float?> source);
public static int Max(this IEnumerable<int> source);
public decimal Max(this IEnumerable<decimal> source);
public decimal? Max(this IEnumerable<decimal?> source);
public static long? Min(this IEnumerable<long?> source);
public static int? Min(this IEnumerable<int?> source);
public static int Min(this IEnumerable<int> source);
public static float? Min(this IEnumerable<float?> source);
public static double Min(this IEnumerable<double> source);
public static double? Min(this IEnumerable<double?> source);
public static decimal Min(this IEnumerable<decimal> source);
public static decimal? Min(this IEnumerable<decimal?> source);
public static long Min(this IEnumerable<long> source);
public static float Min(this IEnumerable<float> source);
public static int Max<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector);
public static int? Max<TSource>(this IEnumerable<TSource> source, Func<TSource, int?> selector);
public static long Max<TSource>(this IEnumerable<TSource> source, Func<TSource, long> selector);
public static long? Max<TSource>(this IEnumerable<TSource> source, Func<TSource, long?> selector);
public static float Max<TSource>(this IEnumerable<TSource> source, Func<TSource, float> selector);
public static float? Max<TSource>(this IEnumerable<TSource> source, Func<TSource, float?> selector);
public static double Max<TSource>(this IEnumerable<TSource> source, Func<TSource, double> selector);
public static double? Max<TSource>(this IEnumerable<TSource> source, Func<TSource, double?> selector);
public static decimal Max<TSource>(this IEnumerable<TSource> source, Func<TSource, decimal> selector);
public static decimal? Max<TSource>(this IEnumerable<TSource> source, Func<TSource, decimal?> selector);
public static long? Min<TSource>(this IEnumerable<TSource> source, Func<TSource, long?> selector);
public static decimal? Min<TSource>(this IEnumerable<TSource> source, Func<TSource, decimal?> selector);
public static decimal Min<TSource>(this IEnumerable<TSource> source, Func<TSource, decimal> selector);
public static float Min<TSource>(this IEnumerable<TSource> source, Func<TSource, float> selector);
public static double? Min<TSource>(this IEnumerable<TSource> source, Func<TSource, double?> selector);
public static int? Min<TSource>(this IEnumerable<TSource> source, Func<TSource, int?> selector);
public static int Min<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector);
public static float? Min<TSource>(this IEnumerable<TSource> source, Func<TSource, float?> selector);
public static long Min<TSource>(this IEnumerable<TSource> source, Func<TSource, long> selector);
public static double Min<TSource>(this IEnumerable<TSource> source, Func<TSource, double> selector);
Y ahora vamos con un ejemplo:
static void Main(string[] args)
{
int[] cifras = new int[] { 1000, 2000, 3000 };
int maxCifras = cifras.Max();
int minCifras = cifras.Min();
Console.WriteLine($"{nameof(maxCifras) } = {maxCifras }");
Console.WriteLine($"{nameof(minCifras) } = {minCifras }");
Console.Read();
}
Un ejemplo un poco más complicado:
static void Main(string[] args)
{
var elementos = new List<Elemento>()
{
new Elemento { ID = 0, Amount = 10000},
new Elemento { ID = 1, Amount = 1000},
new Elemento { ID = 2, Amount = 2000},
new Elemento { ID = 3, Amount = 3000}
};
var maxAmountPorSelect = elementos.Max(a => a.Amount);
var minAmountPorSelect = elementos.Min(a => a.Amount);
var maxAmountPorCampo = elementos.Max(a => a.Amount);
var minAmountPorCampo = elementos.Min(a => a.Amount);
Console.WriteLine($"{nameof(maxAmountPorSelect)} = {maxAmountPorSelect}");
Console.WriteLine($"{nameof(minAmountPorSelect)} = {minAmountPorSelect}");
Console.WriteLine($"{nameof(maxAmountPorCampo) } = {maxAmountPorCampo }");
Console.WriteLine($"{nameof(minAmountPorCampo) } = {minAmountPorCampo }");
Console.Read();
}
Average
Misma fórmula y mismo comportamiento y tratamiento que para
sus hermanos Sum, Max y Min, pero para hallar la media.
En este caso, los tipos de devolución serán siempre
compatibles con datos decimales.
Firmas:
public static float Average(this IEnumerable<float> source);
public static double? Average(this IEnumerable<long?> source);
public static float? Average(this IEnumerable<float?> source);
public static double Average(this IEnumerable<double> source);
public static double Average(this IEnumerable<int> source);
public static decimal Average(this IEnumerable<decimal> source);
public static decimal? Average(this IEnumerable<decimal?> source);
public static double Average(this IEnumerable<long> source);
public static double? Average(this IEnumerable<double?> source);
public static double? Average(this IEnumerable<int?> source);
public static decimal Average<TSource>(this IEnumerable<TSource> source, Func<TSource, decimal> selector);
public static decimal? Average<TSource>(this IEnumerable<TSource> source, Func<TSource, decimal?> selector);
public static double? Average<TSource>(this IEnumerable<TSource> source, Func<TSource, int?> selector);
public static double Average<TSource>(this IEnumerable<TSource> source, Func<TSource, long> selector);
public static double? Average<TSource>(this IEnumerable<TSource> source, Func<TSource, long?> selector);
public static float Average<TSource>(this IEnumerable<TSource> source, Func<TSource, float> selector);
public static float? Average<TSource>(this IEnumerable<TSource> source, Func<TSource, float?> selector);
public static double Average<TSource>(this IEnumerable<TSource> source, Func<TSource, double> selector);
public static double? Average<TSource>(this IEnumerable<TSource> source, Func<TSource, double?> selector);
public static double Average<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector);
Un ejemplo
simple:
static void Main(string[] args)
{
int[] cifras = new int[] { 1000, 2000, 3000 };
var mediaCifras = cifras.Average();
Console.WriteLine($"{nameof(mediaCifras) } = {mediaCifras }");
Console.Read();
}
Uno un poco más complejo:
static void Main(string[] args)
{
var elementos = new List<Elemento>()
{
new Elemento { ID = 0, Amount = 10000},
new Elemento { ID = 1, Amount = 1000},
new Elemento { ID = 2, Amount = 2000},
new Elemento { ID = 3, Amount = 3000}
};
var mediaPorSelect = elementos.Max(a => a.Amount);
var mediaPorCampo = elementos.Max(a => a.Amount);
Console.WriteLine($"{nameof(mediaPorSelect)} = {mediaPorSelect}");
Console.WriteLine($"{nameof(mediaPorCampo) } = {mediaPorCampo}" );
Console.Read();
}
Aggregate
El método extensor Aggregate, principalmente se utiliza para aplicar una
función de acumulación a una colección.
Vamos a ver sus firmas:
public static TSource Aggregate<TSource>(this IEnumerable<TSource> source, Func<TSource, TSource, TSource> func);
public static TAccumulate Aggregate<TSource, TAccumulate>(this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> func);
public static TResult Aggregate<TSource, TAccumulate, TResult>(this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> func, Func<TAccumulate, TResult> resultSelector);
Primera Sobrecarga
En su parte más simple, el operador Aggregate realiza su
tarea llamando al parámetro (Func<TSource, TSource, TSource> func)
por cada uno de los elementos del parámetro source. El parámetro (Func<TSource, TSource, TSource> func)
especifica la acción de acumulación.
Vamos a ver un ejemplo muy simple, que mostraría tabulados los datos de un array de strings:
static void Main(string[] args)
{
string[] nombres = { "Miguel", "Luis", "Gabriel", "Laura", "Maria del Carmen" };
string resultado = nombres.Aggregate((result, next) => result += string.Format("\r\n{0:-16}", next));
Console.WriteLine(resultado);
Console.Read();
}
Segunda Sobrecarga
Esta segunda sobrecarga, es básicamente igual a la primera,
pero con un parámetro TSource más, para indicarle el valor de inicialización
que queremos para nuestro acumulador.
class Program
{
static void Main(string[] args)
{
string[] nombres = { "Miguel", "Luis", "Gabriel", "Laura", "Maria del Carmen" };
string resultado = nombres.Aggregate("Dato Inicial", (result, next) => result += string.Format("\r\n{0:-16}", next));
Console.WriteLine(resultado);
Console.Read();
}
}
Tercera sobrecarga
Esta úlitma sobrecarga, tiene las mismas características de las anteriores, pero añade la alternativa de configurar el formato de salida, mediante el parámetro
Func<TAccumulate, TResult> resultSelector.
Vamos a ver un ejemplo:
class Program
{
static void Main(string[] args)
{
string[] nombres = { "Miguel", "Luis", "Gabriel", "Laura", "Maria del Carmen" };
string resultado = nombres.Aggregate("Dato Inicial",
(result, next) => result += string.Format("\r\n{0:-16}", next),
a => $"{a} \r\n --> Total caracteres: {a.Length} "
);
Console.WriteLine(resultado);
Console.Read();
}
}
Bastante fácil.
Otro ejemplo un poquito más complicado, pero que nos puede
ayudar a entenderlo un pelín mejor:
class Program
{
static void Main(string[] args)
{
string[] nombres = { "Miguel", "Luis", "Gabriel", "Laura", "Maria del Carmen" };
var separador = new string[] { "\r\n" };
var resultado = nombres.Aggregate("Dato Inicial",
(result, next) => result += string.Format("\r\n{0:-16}", next),
a => a.Split(separador, StringSplitOptions.None).Select(b => new { Dato = b, Len = b.Length })
);
resultado.ToList().ForEach(r => Console.WriteLine(r));
Console.Read();
}
}
Importante ver la utilidad que damos al último parámetro de
esta sobrecarga, particionando el string de resultado, para generarnos una
colección de tipos anónimos con un campo descripción y la longitud de
caracteres de cada uno.
La guinda del pastel
Siempre intento dar a mis post algo de originalidad y sobre
todo de definición en contraste con otros ejemplos de la red, y para ello y
como adelantaba al principio, voy a generar el resultado de los operadores Sum,
Min/Max y Average, utiliznado el overador Aggregate:
SUM
static void Main(string[] args)
{
int[] cifras = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var sum = cifras.Aggregate((total, next) => total += next);
Console.WriteLine($"El valor de {nameof(sum)} es {sum}");
Console.Read();
}
MIN
static void Main(string[] args)
{
int[] cifras = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var min = cifras.Aggregate((total, next) => next < total ? next : total);
Console.WriteLine($"El valor de {nameof(min)} es {min}");
Console.Read();
}
MAX
static void Main(string[] args)
{
int[] cifras = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var max = cifras.Aggregate((total, next) => next > total ? next : total);
Console.WriteLine($"El valor de {nameof(max)} es {max}");
Console.Read();
}
AVERAGE
static void Main(string[] args)
{
int[] cifras = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var average = cifras.Aggregate(
0,
(total, next) => total += next,
a => decimal.Parse(a.ToString()) / decimal.Parse(cifras.Count().ToString())
);
Console.WriteLine($"El valor de {nameof(average)} es {average}");
Console.Read();
}
No hay comentarios :
Publicar un comentario