Inicio > .NET Framework, C#, Tutorial > Pininos con LINQ – 3. Agrupaciones

Pininos con LINQ – 3. Agrupaciones


En la primera entrega de este tutorial vimos cómo podemos realizar consultas básicas con LINQ. En la segunda entrega vimos lo que hay tras bambalinas y los métodos que son llamados en realidad. En ambas entregas vimos cómo realizar consultas, filtrarlas, ordenarlas y transformar los resultados.

Sin embargo, hasta el momento he dejado de lado dos temas importantes: las agrupaciones y las uniones. Hice esto porque ambos temas son lo suficientemente extensos como para tener su propia entrega, además de que ninguna de las dos son necesarias para comenzar a utilizar LINQ. Pero creo que ya es tiempo de tratarlas, así que esta entrega está dedicada a las agrupaciones, mientras que la siguiente entrega lo estará a las uniones. Comencemos pues.

Cuando realizamos alguna consulta en LINQ, es posible que queramos agrupar aquellos datos que compartan alguna característica en común. Por ejemplo, supongamos que tenemos un array de cadenas de texto en las cuales guardamos nombres. Por ejemplo, quizás queramos agruparlas por la letra con la que comienzan. Es decir, generar una salida con algo así:

C
- Catalinga
- Claudia
F
- Fernando
- Francisco
J
- Joan
L
- Lorena
M
- Miguel
- Moisés
- Minerva

¿Por qué haríamos algo así? Usualmente, por comodidad. Aunque a un sistema no le importa cómo se agrupen los datos, nuestros usuarios sí que pueden querer visualizarlos de esta forma para identificar algún elemento de forma rápida. Por otra parte, suele ocurrir que queramos operar sobre solo cierto grupo de datos en una colección.

Pues bien, LINQ soporta agrupaciones de forma similar a como las soporta SQL: con una cláusula “group by”.

Lo primero que debemos saber es que cuando hacemos una agrupación, en lugar de recibir un IEnumerable<T>, donde T es el tipo  de dato que regresamos en nuestra cláusula “select”, recibimos un objeto IEnumerable<IGrouping<K, T>>, donde K es el tipo de dato sobre el cual queremos agrupar nuestros datos. En nuestro ejemplo anterior, la consulta regresaría un IEnumerable<IGrouping<char, string>> porque operamos sobre nombres (cadenas de texto) y agrupamos por el primer caracter del nombre, es decir, un char.

La interfaz IGrouping<K, T> hereda de IEnumerable<T> y añade una propiedad: Key, de tipo K. Así pues, Key define el elemento por el cual agrupamos (en nuestro ejemplo, sería el primer caracter de un nombre) mientras que al iterar sobre este objeto (recordemos que hereda de IEnumerable<T>) obtenemos los elementos que coincidieron con el criterio de agrupación (en nuestro ejemplo, los nombres que comienzan con el mismo caracter que Key). Así que ahora tendríamos que hacer dos bucles “foreach”: uno para cada agrupación y otro anidado para los elementos que contenga dicha agrupación.

Pero basta de teoría y veamos el código de nuestro ejemplo.

string str = 
  "Fernando,Catalina,Miguel,Joan,Moisés,Claudia,Francisco,Minerva,Lorena";
string[] names = str.Split(',');

IEnumerable<IGrouping<char, string>> q = from name in names
    orderby name[0] ascending
    group name by name[0];

foreach (IGrouping<char, string> nameGroup in q)
{
  Console.WriteLine(nameGroup.Key);
  foreach (string name in nameGroup)
    Console.WriteLine("- {0}", name);
}

Puedes haber notado algunas diferencias. En primer lugar, la cláusula “select” ha sido reemplazada por “group by”. Esta se forma de la siguiente manera: “group [valor] by [criterio]”. Aquí, [valor] es lo mismo que pondrías en el “select”, siendo nuestro ejemplo la cadena de texto con el nombre; mientras que [criterio] es un valor sobre el cual se realizará la agrupación: en nuestro ejemplo, sería el primer caracter que tenga el nombre. Dado que de alguna manera la cláusula “group by” reemplaza a “select”, ésta tiene que escribirse al final de la consulta (es decir, las cláusulas orderby y where van antes que group by).

Por supuesto, las agrupaciones pueden ser de cualquier tipo de dato y no necesariamente estar relacionados con el tipo de dato original. El siguiente ejemplo agrupa aquellos nombres que tienen más de ocho caracteres (es decir, el valor retornado es IEnumerable<IGrouping<bool, string>>).

string str = 
  "Fernando,Catalina,Miguel,Joan,Moisés,Claudia,Francisco,Minerva,Lorena";
string[] names = str.Split(',');

IEnumerable<IGrouping<bool, string>> q = from name in names
                     orderby name[0] ascending
                     group name by name.Length >= 8;

foreach (IGrouping<bool, string> nameGroup in q)
{
  Console.WriteLine(nameGroup.Key);
  foreach (string name in nameGroup)
    Console.WriteLine("- {0}", name);
}
/* Resultado: 
True
- Catalina
- Fernando
- Francisco
False
- Claudia
- Joan
- Lorena
- Miguel
- Moisés
- Minerva
*/

Dado que escribir IEnumerable<IGrouping<bool, string>> es un tanto engorroso, en adelante haremos uso de “var” para ahorrarnos un poco de escritura.

Podemos también agrupar por valores numéricos:

...
var q = from name in names
  orderby name.Length ascending, name ascending
  group name by name.Length;
...
/* Resultado: 
4
- Joan
6
- Lorena
- Miguel
- Moisés
7
- Claudia
- Minerva
8
- Catalina
- Fernando
9
- Francisco
*/

Nota que además hicimos un doble criterio de ordenamiento: primer ordenar por la longitud de la cadena, y luego ordenamos por la cadena misma.

De igual forma, podemos agrupar por tipos de datos que hayamos creado y también podemos valernos de la oportunidad de crear tipos de datos anónimos. En el siguiente ejemplo regresamos a nuestra querida clase Employee que desarrollamos en las pasadas entregas.

using System;
using System.Linq;

namespace Fermasmas.Wordpress.Com
{
  enum Gender { Male, Female, Unknown }

  class Employee : IComparable
  {
    public string Name;
    public int Age;
    public float Income;
    public Gender Gender;

    public Employee()
      : this(string.Empty, 0, 0F, Gender.Unknown)
    {
    }

    public Employee(string name, int age, float income, Gender gender)
    {
      Name = name;
      Age = age;
      Income = income;
      Gender = gender;
    }

    public int CompareTo(object obj)
    {
      if (obj == null)
        throw new ArgumentNullException("obj");
      if (!(obj is Employee))
        throw new ArgumentException("obj debe ser de tipo Employee", "obj");

      Employee emp = obj as Employee;
      return Name.CompareTo(emp.Name);
    }

    public override string ToString()
    {
      return Name;
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      Employee[] employees = new Employee[]
      {
        new Employee("Fernando Gómez", 27, 20000F, Gender.Male),
        new Employee("Katia De Montanaro", 29, 30000F, Gender.Female),
        new Employee("Moisés Pedraza", 30, 25000F, Gender.Male),
        new Employee("Joan Hernández", 25, 20000F, Gender.Male),
        new Employee("Claudia Estrella", 27, 15000F, Gender.Female)
      };

      var q = from employee in employees
          orderby employee.Name ascending
          group employee by employee.Gender;

      foreach (var nameGroup in q)
      {
        Console.WriteLine(nameGroup.Key);
        foreach (var name in nameGroup)
          Console.WriteLine("- {0}", name);
      }

      Console.ReadKey(true);
    }
  }
}

Al correr este ejemplo, tenemos el siguiente resultado.

Female
- Claudia Estrella
- Katia De Montanaro
Male
- Fernando Gómez
- Joan Hernández
- Moisés Pedraza

Como puedes ver, las agrupaciones son una forma útil de presentar nuestros datos. Un ejemplo práctico (que por cierto, desarrollaremos al final de este tutorial) puede ser una vista en Windows Forms donde tengamos un ListView donde podamos fácilmente agrupar y ordenar lo que se presenta, sin necesidad de realizar nuevas consultas a la base de datos (digo, si ya tenemos nuestro DataSet).

Bien, ya para finalizar… en la entrega anterior mostramos que los operadores estándares de consulta en realidad el compilador los transforma en llamadas a métodos de extensión. Pues bien, la cláusula group…by no es excepción. Ésta se tranforma en una llamada al método GroupBy que toma como parámetro una expresión lambda que nos devuelve el valor sobre el cual queremos agrupar. Así, el primer ejemplo que mostramos podría reescribirse como:

string str =
  "Fernando,Catalina,Miguel,Joan,Moisés,Claudia,Francisco,Minerva,Lorena";
string[] names = str.Split(',');

IEnumerable<IGrouping<char, string>> q = names.OrderBy(x => x[0])
                        .GroupBy(x => x[0]);
  
foreach (IGrouping<char, string> nameGroup in q)
{
  Console.WriteLine(nameGroup.Key);
  foreach (string name in nameGroup)
    Console.WriteLine("- {0}", name);
}

y obtendríamos el mismo resultado:

C
- Catalinga
- Claudia
F
- Fernando
- Francisco
J
- Joan
L
- Lorena
M
- Miguel
- Moisés
- Minerva

mientras que el ejemplo con nuestra clase Employee lo podemos traducir como:

static void Main(string[] args)
{
  Employee[] employees = new Employee[]
  {
	new Employee("Fernando Gómez", 27, 20000F, Gender.Male),
	new Employee("Katia De Montanaro", 29, 30000F, Gender.Female),
	new Employee("Moisés Pedraza", 30, 25000F, Gender.Male),
	new Employee("Joan Hernández", 25, 20000F, Gender.Male),
	new Employee("Claudia Estrella", 27, 15000F, Gender.Female)
  };

  var q = employees.OrderBy(x => x.Name)
		   .GroupBy(x => x.Gender);

  foreach (var nameGroup in q)
  {
	Console.WriteLine(nameGroup.Key);
	foreach (var name in nameGroup)
	  Console.WriteLine("- {0}", name);
  }

  Console.ReadKey(true);
}

y evidentemente, obtendríamos el mismo resultado que se mostró anteriormente.

Bueno, eso es todo por el momento. En la siguiente entrega veremos como hacer uniones, un tema bastante amplio por sí mismo. Pero mientras, espero que te diviertas con las agrupaciones.

Anuncios
Categorías:.NET Framework, C#, Tutorial Etiquetas: ,
  1. Aún no hay comentarios.
  1. No trackbacks yet.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s