Tabla de contenidos:
GroupBy
El operador GroupBy nos permite agrupar una sencuencia bajo una clave.
- Código necesario para los ejemplos
- GroupBy estándar
- GroupBy + comparador personalizado
- GroupBy + proyector sencillo
- GroupBy + proyector de enumeración
- GroupBy + proyector sencillo + comparador personalizado
- GroupBy + proyector sencillo + proyector de enumeración
- GroupBy + proyector de enumeración + comparador personalizado
- GroupBy + proyector simple + proyector de enumeración + comparador personalizado
Código necesario para los ejemplos:
Una clase persona:
public class Persona
{
public string Nombre { get; set;}
public int Edad { get; set;}
public string Ciudad { get; set; }
public Persona(string nombre, int edad, string ciudad)
{
Nombre = nombre;
Edad = edad;
Ciudad = ciudad;
}
}
Una lista de personas:
List<Persona> personas = new List<Persona>
{
new Persona("Rodriguez, Jesús", 24, "Cádiz"),
new Persona("Bautista, Jesús", 15, "Alicante"),
new Persona("Perez, Juan", 15, "Cádiz"),
new Persona("García, Javier", 24, "Málaga"),
new Persona("Toledo, María", 37, "Málaga")
};
Un comparador personalizado:
public class MayoriaEdad : IEqualityComparer<int>
{
public bool Equals(int x, int y)
{
return (EsMayor(x) == EsMayor(y));
}
public int GetHashCode(int edad)
{
int menor = 1;
int mayor = 18;
return (EsMayor(edad) ? mayor.GetHashCode() : menor.GetHashCode());
}
public bool EsMayor(int edad)
{
return (edad >= 18);
}
}
GroupBy estándar:
public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector )
El operador GroupBy estándar simplemente recibe un delegado al que le pasamos la clave que usaremos para agrupar los elementos de la secuencia. Devuelve una enumeración de elementos que tienen una clave común.
Queremos agrupar nuestra lista de personas en grupos donde la clave sea la ciudad. Para ello debemos de indicarle a GroupBy que como clave queremos usar ciudad:
IEnumerable<IGrouping<string, Persona>> personasPorCiudad = personas.GroupBy(persona => persona.Ciudad);
Nada dificil, le decimos que queremos agrupar la secuencia por la ciudad.
También podemos agrupar usando un query expression:
IEnumerable<IGrouping<string, Persona>> personasPorCiudad = from persona in personas group persona by persona.Ciudad into perCiudad select perCiudad;
Por cada persona agrupamos dicha persona por su ciudad en perCiudad y simplemente devolvemos perCiudad.
En ambos casos podríamos imprimir el resultado de esta forma:
foreach (var ciudad in personasPorCiudad)
{
Console.WriteLine(ciudad.Key);
foreach (var persona in ciudad)
{
Console.WriteLine("{0} ({1})", persona.Nombre, persona.Edad);
}
Console.WriteLine();
}
Y obtener algo así:
Cádiz
Rodriguez, Jesús (24)
Perez, Juan (15)
Alicante
Bautista, Jesús (15)
Málaga
García, Javier (24)
Toledo, María (37)
GroupBy + comparador personalizado:
public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer )
Esta sobrecarga hace lo mismo que el GroupBy estándar con la diferencia de que usa un comparador personalizado a la hora de agrupar los elementos.
Hemos creado un comparador personalizado, el cual he adjuntado en el código de este operador, que nos sirve para agrupar las personas por mayores o menores de edad.
IEnumerable<IGrouping<int, Persona>> personasPorMayoria = personas.GroupBy(p => p.Edad, comp);
Ahora tenemos la secuencia agrupadas por mayores de edad y por menores de edad.
Algo así:
Mayor
24 - Rodriguez, Jesús (Cádiz)
24 - García, Javier (Málaga)
37 - Toledo, María (Málaga)
Menor
15 - Bautista, Jesús (Alicante)
15 - Perez, Juan (Cádiz)
GroupBy + proyector sencillo:
public static IEnumerable<IGrouping<TKey, TElement>> GroupBy<TSource, TKey, TElement>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector )
En esta sobrecarga podemos usar una función para proyectar los elementos de cada grupo.
Por ejemplo, vamos a agrupar por la ciudad pero esta vez solo queremos proyectar el nombre, no necesitamos la edad para nada:
IEnumerable<IGrouping<string, string>> perCiudad = personas.GroupBy(p => p.Ciudad, p => p.Nombre);
La diferencia con el operador estandar es que ya no estamos proyectando todo el objeto Persona, sólo estamos proyectando el nombre.
El resultado sería:
Cádiz
Rodriguez, Jesús
Perez, Juan
Alicante
Bautista, Jesús
Málaga
García, Javier
Toledo, María
GroupBy + proyector de enumeración:
public static IEnumerable<TResult> GroupBy<TSource, TKey, TResult>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TKey, IEnumerable<TSource>, TResult> resultSelector )
En esta sobrecarga, vamos a agrupar los elementos bajo una clave común pero usamos un segundo delegado el cual recibe la clave, una enumeración con los elementos agrupados bajo dicha clave y devuelve un elemento como resultado. Finalmente el operador devuelve una enumeración de dichos resultados.
Como es algo complicado de entender (y aún más de explicar sin ejemplos) vamos con un ejemplo. Vamos a agrupar por ciudad y lo que queremos saber es cuantas personas de nuestra lista residen en dichas ciudades:
var infoCiudades = personas.GroupBy(p => p.Ciudad,
(ciudad, perCiudad) => new
{
Ciudad = ciudad,
NumPersonas = perCiudad.Count()
});
Le hemos indicado que queremos agrupar por ciudad, y luego hemos creado un tipo anónimo que contiene la clave y la cantidad de personas por cada ciudad.
Ahora si volvemos a leer la descripción de esta sobrecarga, nos queda más claro. Estamos creando una enumeración de tipos anónimos compuestos por la ciudad y por la cantidad de personas de dicha ciudad.
El resultado es:
Ciudad=Cádiz NumPersonas=2
Ciudad=Alicante NumPersonas=1
Ciudad=Málaga NumPersonas=2
GroupBy + proyector sencillo + comparador personalizado:
public static IEnumerable<IGrouping<TKey, TElement>> GroupBy<TSource, TKey, TElement>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey> comparer )
͉sta sobrecarga es una mezcla de 2 sobrecargas que ya hemos visto. Agrupa los elementos dada una clave, proyecta la parte que necesitamos de cada elemento agrupado y para hacer la agrupación.
Así que vamos a hacer una mezcla entre los dos ejemplos de dichas sobrecargas. Vamos a agrupar por mayores o menores de edad y luego de los elementos agrupados, solo vamos a proyectar el nombre (descartando la ciudad y la edad):
IEnumerable<IGrouping<int, string>> porMayoria = personas.GroupBy(p => p.Edad, p => p.Nombre, comp);
El resultado sería algo así:
Mayor
Rodriguez, Jesús
García, Javier
Toledo, María
Menor
Bautista, Jesús
Perez, Juan
GroupBy + proyector sencillo + proyector de enumeración:
public static IEnumerable<TResult> GroupBy<TSource, TKey, TElement, TResult>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, Func<TKey, IEnumerable<TElement>, TResult> resultSelector )
Esta sobrecarga, agrupa los elementos dada una clave, proyecta solo la parte que necesitamos de cada elemento agrupado y por último pasamos cada clave y cada proyección a otro delegado el cual crea un elemento resultante. El operador devuelve una enumeración de dicho elemento.
Vamos a volver a volver a agrupar a las personas por ciudad y vamos a contar cuantas personas hay en cada ciudad:
var perCiudad = personas.GroupBy(p => p.Ciudad,
p => p.Ciudad,
(key, ciudades) => new
{
Ciudad = key,
NumPersonas = ciudades.Count()
});
El ejemplo es el mismo que hemos usado en la sobrecarga del proyector de enumeración. La diferencia es que aquí en vez de pasarle una enumeración de personas le he pasado una enumeración de cadenas.
La idea es que como no necesitamos toda la información que nos provee la clase persona, proyectamos simplemente aquella parte que necesitamos (aunque puntualizando, aqui realmente no necesitamos ni la edad, ni el nombre ni la ciudad, pero tenemos que elegir al menos un elemento a proyectar).
El resultado es el mismo:
Ciudad=Cádiz NumPersonas=2
Ciudad=Alicante NumPersonas=1
Ciudad=Málaga NumPersonas=2
GroupBy + proyector de enumeración + comparador personalizado:
public static IEnumerable<TResult> GroupBy<TSource, TKey, TResult>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TKey, IEnumerable<TSource>, TResult> resultSelector, IEqualityComparer<TKey> comparer )
Esta sobrecarga tiene el delegado de enumeración que ya hemos visto en dos ocasiones y además soporta un comparador personalizado.
Vamos a agrupar por mayoria o minoría de edad (usando nuestro comparador) y luego vamos a proyectar la cantidad de personas en cada grupo:
var infoEdades = personas.GroupBy(p => p.Edad,
(edad, pers) => new
{
Condicion = MayorMenor(edad),
NumPersonas = pers.Count()
},
comp);
Hemos usado un pequeño método de ayuda:
public string MayorMenor(int edad)
{
return edad < 18 ? "Menores de edad" : "Mayores de edad";
}
Bueno, nada nuevo realmente por aquí. El comparador siempre usa la clave para comparar y simplemente sacamos información de cada grupo.
El resultado es:
Mayores de edad
Numero personas: 3
Menores de edad
Numero personas: 2
GroupBy + proyector simple + proyector de enumeración + comparador personalizado:
public static IEnumerable<TResult> GroupBy<TSource, TKey, TElement, TResult>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, Func<TKey, IEnumerable<TElement>, TResult> resultSelector, IEqualityComparer<TKey> comparer )
He aquí la combinación de todas las sobrecargas de este pequeño operador. Tiene su proyector de elementos, proyecta luego una enumeración y todo con su comparador personalizado.
Vamos a agrupar por mayoría o minoría de edad, y vamos a sacar varios datos sobre dichas edades:
var infoEdades = personas.GroupBy(p => p.Edad,
p => p.Edad,
(edad, edades) => new
{
Condicion = MayorMenor(edad),
Min = edades.Min(),
Max = edades.Max()
},
comp);
Agrupamos por edad, proyectamos solo la edad puesto que es lo único que necesitamos, guardamos la edad mínima y máxima del grupo y usamos el comparador personalizado para hacer las agrupaciones.
El resultado es:
Mayores de edad
Edad mínima: 24
Edad máxima: 37
Menores de edad
Edad mínima: 15
Edad máxima: 15
Volver a la tabla de contenidos
ToLookup
El operador ToLookup es bastante parecido a GroupBy en cuanto uso, la diferencia es que este operador crea un objeto del tipo Lookup como veremos en los ejemplos
- Código necesario para los ejemplos
- ToLookup estándar
- ToLookup + comparador personalizado
- ToLookup + proyector
- ToLookup + proyector + comparador personalizado
Código necesario para los ejemplos:
Una clase persona:
public class Persona
{
public string Nombre { get; set;}
public int Edad { get; set;}
public string Ciudad { get; set; }
public Persona(string nombre, int edad, string ciudad)
{
Nombre = nombre;
Edad = edad;
Ciudad = ciudad;
}
}
Una lista de personas:
List<Persona> personas = new List<Persona>
{
new Persona("Rodriguez, Jesús", 24, "Cádiz"),
new Persona("Bautista, Jesús", 15, "Alicante"),
new Persona("Perez, Juan", 15, "Cádiz"),
new Persona("García, Javier", 24, "Málaga"),
new Persona("Toledo, María", 37, "Málaga")
};
Un comparador personalizado:
public class MayoriaEdad : IEqualityComparer<int>
{
public bool Equals(int x, int y)
{
return (EsMayor(x) == EsMayor(y));
}
public int GetHashCode(int edad)
{
int menor = 1;
int mayor = 18;
return (EsMayor(edad) ? mayor.GetHashCode() : menor.GetHashCode());
}
public bool EsMayor(int edad)
{
return (edad >= 18);
}
}
ToLookup estándar:
public static ILookup<TKey, TSource> ToLookup<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector )
El operador ToLookup recibe un delegado que usaremos para seleccionar la clave. Este operador devuelve un objeto que implemente la interfaz ILookup
ILookup implementa a su vez IEnumerable
ILookup<string, Persona> perCiudad = personas.ToLookup(p => p.Ciudad);
Aquí creamos un objeto que implementa la interfaz ILookup. En este caso la la clave es una cadena (la ciudad) y como elemento contiene una enumeración de personas (que vivan en esa ciudad).
¿Es lo mismo que GroupBy, no? No exactamente, Imaginemos que necesitamos las personas que sean de Cádiz, pues ahora podemos hacer:
IEnumerable<Persona> gaditanos = perCiudad["Cádiz"];
Como podéis ver, el objeto tiene un indizador, así que podemos acceder aquellos elementos que queramos simplemente especificando la clave.
Imprimir esa variable daría algo del tipo:
Nombre=Rodriguez, Jesús Edad=24 Ciudad=Cádiz
Nombre=Perez, Juan Edad=15 Ciudad=Cádiz
ToLookup + comparador personalizado:
public static ILookup<TKey, TSource> ToLookup<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer )
Esta sobrecarga toma además un comparador personalizado para seleccionar la clave. Veamoslo con un ejempo:
ILookup<int, Persona> mayorOMenor = personas.ToLookup(p => p.Edad, new MayoriaEdad());
͉sta es la parte sencilla de entender. Creamos un objeto que implementa ILookup y para comparar las claves usamos nuestro comparador personalizado.
Ahora la parte dificil de entender:
IEnumerable<Persona> mayores = mayorOMenor[20];
No hay nadie con 20 años, ¿no? Es cierto, pero no es lo que le estamos pidiendo.
Como sabréis, la mayoría de operadores de linq implementan la ejecución diferida. ¿Qué significa esto? Que cuando ejecutas el operador no estás realmente creando ninguna colección ni nada. Lo que obtienes del operador es un objeto que almacena la información necesaria para realizar la acción de dicho operador.
Así que cuando ejecutamos la sentencia anterior, lo que va a hacer es enviar el valor del indizador (en nuestro caso 20) al comparador, ahi va a compararlo con todas las edades. Algo así:
Toma la edad que le hemos pasado (20) y comprueba que es una edad sobre 18. Luego va tomando cada valor de la lista. Por ejemplo 15, no es mayor de edad así que descartada, luego coge 24, tambien es mayor de edad, y como ambas lo son, devuelve la persona con dicha edad.
En resumen, con esta sobrecarga, puedes pasarle cualquier número al objeto ILookup que si es mayor de 18 (mayoría de edad) devolverá todos los mayores de edad, y si es un numero menor a 18, devolverá aquellos que no lo son. Con la otra sobrecarga solo podías meterle valores especificos para que funcionase.
Así que si le pasamos 20 conseguiremos:
Nombre=Rodriguez, Jesús Edad=24 Ciudad=Cádiz
Nombre=García, Javier Edad=24 Ciudad=Málaga
Nombre=Toledo, María Edad=37 Ciudad=Málaga
ToLookup + proyector:
public static ILookup<TKey, TElement> ToLookup<TSource, TKey, TElement>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector )
En esta sobrecarga tenemos la posibilidad de definir como serán los elementos asociados a las claves.
Volviendo al ejemplo de agrupar por ciudades. Nosotros agrupabamos por ciudad, luego le decíamos que queríamos todas aquellas personas que fueran de Cádiz. El “problema” es que cada objeto que nos devuelve también contiene una propiedad del tipo Ciudad lo cual es redundante, puesto que ya sabemos que son de Cádiz.
Podríamos solucionar eso de la siguiente forma:
var perCiudad = personas.ToLookup(p => p.Ciudad, p => new {p.Nombre, p.Edad});
Hemos creado un objeto que implementa ILookup pero esta vez, aparte de agrupar por ciudad, hemos creado un tipo anónimo que solo contiene el nombre y la edad descartando la ciudad (pues ya la sabemos a la hora de hacer una consulta).
Se usaría como siempre:
var gaditanos = perCiudad["Cádiz"];
Y el resultado:
Nombre=Rodriguez, Jesús Edad=24
Nombre=Perez, Juan Edad=15
ToLookup + proyector + comparador personalizado:
public static ILookup<TKey, TElement> ToLookup<TSource, TKey, TElement>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey> comparer )
Esta sobrecarga es una mezcla entre las 3 anteriores. Agrupamos dada una clave, proyectamos aquello que necesitamos y usamos un comparador personalizado para la agrupación.
Vamos a agrupar por edad como hicimos antes, proyectamos solo nombre – ciudad y usamos nuestro comparador:
var mayorOMenor = personas.ToLookup(p => p.Edad,
p => new {p.Nombre, p.Ciudad},
new MayoriaEdad());
Y pedimos a todos los menores (se puede poner cualquier edad que sea menor a 18):
var menores = mayorOMenor[17];
El resultado es:
Nombre=Bautista, Jesús Ciudad=Alicante
Nombre=Perez, Juan Ciudad=Cádiz
Volver a la tabla de contenidos
Tags: LINQ

