Continuamos avanzando dentro de los operadores de consulta
de LinQ, y ahora le toca el turno a Group By. Group By es otro de los operadores de consulta de proyección,
que a grandes rasgos, nos permite desde una colección de entrada, devolver una
colección de salida agrupada, mediante un sencillo grupo de claves valor. Donde la clave estará
compuesta por los campos por los que hemos agrupados (los que forman el group by) y el valor lo formarán una
colección de elementos que comparten los mismos valores para las propiedades
que forman la clave.
Hasta ahora, prácticamente todos nuestros métodos
extensores de LinQ,
devolvían una ‘Colección’ (un objeto del tipo de datos de IEnumerable<TElement>),
el operador Group By, por su
definición devolverá un conjunto de objetos
de un tipo de datos un poco más avanzado IGrouping<out
TKey, out TElement>. Esta interfaz
implementa también IEnumerable<TElement>
, conteniendo a su vez la propiedad Key
que representa el atributo común a todos los elementos de los objetos de su
grupo.
Algo a señalar de la interfaz IGrouping<out
TKey, out TElement> es que sus parámetros de tipo son out, por lo que pueden disfrutar de
las bondades de la
covarianza y contravarianza que también hemos visto con anterioridad y que
forma parte de los delegados genéricos.
IEnumerable<IGrouping<TKey,
Tsource>> será el tipo de datos de resultado de la clausula Group By.
Ejemplo de agrupación por un campo
simple
Vamos a comenzar con un ejemplo de agrupación sencillo, por
un único campo. En este ejemplo la Key
la formará una única propiedad de nuestro objeto (TElement), generando grupos que pertenezcan a esta propiedad:
Este ejemplo tan sencillo lo trasladaríamos así al código:
Apuntar que la clase Color del campo del mismo nombre, está
en el ensamblado System.Windows.Media.
Vamos a ver el código de agrupación:
En la primera sentencia, que aparece comentada podemos ver
el uso de group by, mediante Lambda. Justo debajo, tenemos el
ejemplo de operador de consulta, en este hemos utilizado la sentencia into (que estudiaremos un poco más adelante), para
agregar una especie de variable local, que contendrá nuestros grupos y un tipo anónimo de salida, en el que
hemos encapsulado las dos partes más importante del group by, la clave
(Key) y los registros resultantes de
la agrupación mediante esa clave, al que le hemos añadido el alias de Datos.
A la hora de imprimir los datos, simplemente recorremos un
bucle anidado, que representa nuestro group by,
que no viene a ser más que una colección de colecciones.
Con el
siguiente resultado:
Ejemplo de agrupación por varios
campos (Tipo anónimo)
En este caso, la agrupación la realizaremos por más de un
campo, por lo que nuestra Key
o clave estará formada por más de una propiedad. Para este menester nos
podríamos crear una clase que definiera un tipo con los campos que necesitamos
para la agrupación, pero lo más sencillo es tirar de los tipos
anónimos, que ya estudiamos con anterioridad y que nos ahorrarán escribir
código.
En este ejemplo agruparemos por las propiedades Marca y Carburante:
Código:
El código es muy similar al caso de agrupación simple, con
la diferencia de que utilizamos un tipo anónimo para realizar la agrupación
compuesta por las propiedades Marca
y Carburante. Esto queda también
marcado a la hora de recorrer los datos ya que nuestra Key es compuesta y ahora tiene 2 propiedades.
Resultado:
Como dijimos al principio la cláusula group by, forma partes del grupo de proyección o transformación, por lo que si nos fijamos en el la
sentencia que estaba comentada en el ejemplo anterior y que utilizaba Lambdas, en ella no aparece para
nada el operador select:
Esto viene a evidenciar que ella haría sola la
transformación y que para nada requeriría la ayuda del operador select. En nuestro ejemplo anterior,
lo utilizamos para generar un tipo anónimo, que hiciera más legible el código
de impresión y los bucles, pero perfectamente está sentencia podría haberse
reducido como se muestra:
Agrupación por varios campos
(IEqualityComparer)
Bueno pues vamos a añadir un toque un poco de exclusividad
al post, con un ejemplo de agrupación utilizando IEqualityComparer,
así incorporamos el primer post de este tipo (ya que no he sido capaz de
encontrar ninguno en toda la red) y le otorgamos un poquito más de valor a mi última
entrada.
Normalmente es bastante extraño necesitar un IEqualityComparer, para realizar una
agrupación, habitualmente se suelen emplear más en operadores como: Unión, Distinct, Intersect,
etc., y que investigaremos un poquito más adelante. Me gusta adicionar estos
ejemplos de casos extremos, porque te permiten pensar un poco más de lo normal,
y si eres capaz de comprenderlos te hacen estar un poco más cerca de la
solución de dilemas futuros.
Pongamos que tenemos las siguientes clases:
Lo único peculiar de estas clases, es que PersonaReducida, tiene una propiedad
de tipo definido Direccion.
Ahora pongámonos en la tesitura de que nuestras reglas de
negocio nos obligan a realizar una agrupación por la propiedad Domicilio de tipo Direccion, como muestra el siguiente
código:
Code:
Como se puede apreciar, podríamos pensar que las dos
primeras personas tienen el mismo domicilio y deberían aparecer dentro del mismo
grupo, pero si ejecutamos el código, este es el resultado:
El resultado, es que se nos crea un grupo diferente para
cada una de ellas. Esto es debido a que estamos realizando la agrupación por un
tipo definido por nosotros mismos, ósea por una clase, que aunque sus propiedades contengan los mismos valores,
sus reglas de comparación se rigen por sus referencias
y no por el valor de sus propiedades.
En los ejemplos anteriores la propiedad por la que
agrupábamos era una clase de tipo string,
un struct o un tipo anónimo, que al contrario que las clases comunes, basan
sus reglas de comparación por su valor y no por su referencia. Denotar aquí que
aunque el tipo string sea una clase,
esta es una clase especial ya que es inmutable,
y tienen la peculiaridad de re-instanciarse cada vez que su valor cambia. Las estructuras y los delegados también son inmutables. Los tipos
anónimos, tienen la singularidad de observar el valor de sus propiedades
para denotar si dos objetos son iguales. Pero bueno esto nos daría para otro
post, si hay alguien interesado que me lo pida y lo haré encantadísimo.
Después de este pequeño inciso, continuamos con lo nuestro …
Teníamos el problema que aparentemente para nosotros la
primera persona y la segunda tenían el mismo, pero al ser referencias
diferentes, estaban en grupos diferentes. Para arreglar esto echaremos mano de
nuestro IEqualityComparer
y generaremos una forma de comparación compleja y personalizada para nuestro
problema del tipo Direccion,
del que facilitaremos una instancia al método extensor GroupBy.
Generamos la clase que hereda de IEqualityComparer:
Lo que hemos hecho, simplemente es indicar que para que dos
objetos de tipo Direccion sean
iguales, tienen que tener el mismo valor sus propiedades Calle, Numero
y Provincia.
Volvemos a nuestro código, pero ahora con una pequeña
diferencia:
Code:
Ahora le decimos la manera en que queremos que diferencie
los direcciones:
Y ahora el resultado si es el esperado:
Un apunte a tener en cuenta, como podéis observar en este
ejemplo, no hemos utilizado sintaxis de expresión de consulta, y es que para
este tipo de patrón en el que se utilizan sobrecargas de los métodos
extensores, solo tiene cabida el uso de su expresión
lambda.
Uso práctico de GroupBy
Dentro de nuestro día a día y del trabajo diario, en este
caso hablo del mío, una de las tareas que solemos realizar en ocasiones
comúnmente, es la comprobación de registros duplicados, dentro de ficheros en
ocasiones mastodónticos. En estos casos una simple qry de LinQ, nos hará todo el trabajo sucio.
Supongamos que tenemos un fichero .csv de ejemplo con 4 columnas como este:
Lo dejo en texto por si alguien quisiera copiar los datos:
PROPIEDAD1;PROPIEDAD2;PROPIEDAD3;PROPIEDAD4
1;Dato1;10/01/2015;1000
2;Dato2;11/01/2015;1001
3;Dato3;12/01/2015;1002
4;Dato4;13/01/2015;1003
5;Dato5;14/01/2015;1004
6;Dato6;15/01/2015;1005
7;Dato7;16/01/2015;1006
9;Dato9;17/01/2015;1007
9;Dato9;18/01/2015;1008
10;Dato10;19/01/2015;1009
11;Dato11;20/01/2015;1010
12;Dato12;21/01/2015;1011
13;Dato13;22/01/2015;1012
14;Dato14;23/01/2015;1013
15;Dato15;24/01/2015;1014
16;Dato16;25/01/2015;1015
17;Dato17;26/01/2015;1016
18;Dato18;27/01/2015;1017
19;Dato19;28/01/2015;1018
20;Dato20;29/01/2015;1019
21;Dato21;30/01/2015;1020
Lo leemos y lo pasamos a una clase de negocio como esta:
Esta simple sentencia nos daría los registros duplicados
dentro del fichero:
No hay comentarios :
Publicar un comentario