lunes, 15 de junio de 2015

Set Operators, Concat, Union, Intersect, Except y Distinct




Seguimos con los operadores de LinQ, ahora le toca el turno a los ‘Set Operators’ u Operadores de Conjunto.

Los operadores de conjunto, nos permiten realizar actuaciones sobre dos secuencias (IEnumerable<T>) del mismo tipo de datos, dando como resultado una colección de este mismo tipo. Todas y cada una de sus ejecuciones se realizan de forma diferida o perezosa.

En caso de que alguna de estas colecciones sea null en el momento de realizar la llamada, el compilador lanzará un ArgumentNullException.

Vamos a detallar cada uno de sus operadores, para intentar que todo esto quede un poco más claro, haré un par de ejemplos para cada operador, uno con datos simples, colecciones de tipo int, y otro con una de las clases más utilizada en nuestros ejemplos y que tiene su definición en los post anteriores, el tipo Automovil. El objetivo  es comprender lo imprescindible, que es el uso de las sobrecargas que admiten un comparador de tipo IEQualityComparer<T> para los tipos compuestos, en el que indicaremos al compilador como queremos que se diferencien 2 objetos de este tipo.


Como en todas las reglas, hay una excepción y esta no iba a ser menos. El operador Distinct, cambiará, para utilizar una única colección en decremento de dos, pero eso lo contaremos un poquito más adelante.







Recuerda que aquí tienes el indice de todos los posts del Curso de LinQ.





Concat Operator

El operador Concat, enlaza dos conjuntos de elementos, para construir una colección de resultado con todos y cada uno de los elementos de las dos colecciones. Su homónimo en SQL, sería el operador UnionAll.


En este caso y dado la simplicidad del método extensor, voy a poner el código real de la implementación del mismo y no simplemente la firma:


public static IEnumerable<TSource> Concat<TSource>(IEnumerable<TSource> first, IEnumerable<TSource> second)
{
    if (first == null) throw Error.ArgumentNull("first");
    if (second == null) throw Error.ArgumentNull("second");

    foreach (TSource element in first) yield return element;
    foreach (TSource element in second) yield return element;
}


Aunque el operador Concant, no tenga sobrecarga con comparador, voy a incluir también 2 ejemplos para continuar en concordancia con todos los del post.

Vamos con el ejemplo de datos simple (struct de tipo int):


static void Main(string[] args)
{
    var numeros1 = Enumerable.Range(1, 5).ToList(); /// 1, 2, 3, 4, 5
    var numeros2 = Enumerable.Range(3, 5).ToList(); /// 3, 4, 5, 6, 7
 
    var numerosConcatenados = numeros1.Concat(numeros2).OrderBy(n => n).ToList();
 
    numerosConcatenados.ForEach(n => Console.WriteLine("{0}", n));
 
    Console.Read();
}


Resultado:
















Ejemplo con tipo compuesto (class Automovil):


static void Main(string[] args)
{
    var coches = new List<Automovil>
    {
        new Automovil { Marca = "Renault", Modelo = "Clio"  , Carburante = TipoCarburante.Gasolina, Color = Colors.Green },
        new Automovil { Marca = "Citroen", Modelo = "C3"    , Carburante = TipoCarburante.Diesel  , Color = Colors.Gray  },
        new Automovil { Marca = "Renault", Modelo = "Laguna", Carburante = TipoCarburante.Diesel  , Color = Colors.Black },
        new Automovil { Marca = "Renault", Modelo = "Megane", Carburante = TipoCarburante.Gasolina, Color = Colors.Blue  },
        new Automovil { Marca = "Citroen", Modelo = "C4"    , Carburante = TipoCarburante.Gasolina, Color = Colors.Black }
    };
 
    var coches2 = new List<Automovil>
    {
        new Automovil { Marca = "Renault", Modelo = "Clio" , Carburante = TipoCarburante.Gasolina, Color = Colors.Green },
        new Automovil { Marca = "Seat", Modelo = "Ibiza"   , Carburante = TipoCarburante.Diesel  , Color = Colors.Gray  },
        new Automovil { Marca = "Fian", Modelo = "Punto"   , Carburante = TipoCarburante.Diesel  , Color = Colors.Black },
        new Automovil { Marca = "Citroen", Modelo = "C4"   , Carburante = TipoCarburante.Gasolina, Color = Colors.Coral }
    };
 
 
    var concatenacionCoches = coches.Concat(coches2);
    var concatenacionCochesOrdenados = concatenacionCoches.OrderBy(c => c.Marca).ThenBy(c => c.Modelo).ToList();
 
    coches.Clear();
 
    Console.WriteLine("El resultado de la concatenación es:");
 
    foreach (var coche in concatenacionCochesOrdenados)
    {
        Console.WriteLine("{0, -10} - {1, -6} - {2}", coche.Marca, coche.Modelo, coche.Color.ToString());
    }
 
 
 
    Console.Read();
}


Resultado:

















Como podemos ver, en el resultado aparecen todos y cada uno de los elementos de ambas colecciones.Lo hemos ordenado por Marca y por Modelo, mediante OrderBy y ThenBy, estos operadores los veremos un poquito más adelante.



Operador Union

El operador Union, como su propio nombre indica, une dos secuencias en una, pero en este caso no con todos los elementos de cada una de las colecciones, solo con los diferentes. Si hay algún elemento repetido en ambas, este solo se añadirá una única vez.


El operador Union a diferencia de Concat, si que contiene una segunda sobrecarga que acepta un comparador por parámetros.


public static IEnumerable<TSource> Union<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second) 
public static IEnumerable<TSource> Union<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer) 


Vamos con el ejemplo de datos simple (struct de tipo int):

static void Main(string[] args)
{
    var numeros1 = Enumerable.Range(1, 5).ToList(); /// 1, 2, 3, 4, 5
    var numeros2 = Enumerable.Range(3, 5).ToList(); /// 3, 4, 5, 6, 7
 
    var numerosUnidos = numeros1.Union(numeros2).OrderBy(n => n).ToList();
 
    numerosUnidos.ForEach(n => Console.WriteLine("  {0}", n));
 
    Console.Read();
}


Resultado:













Con el operador Union, decimos adiós a los datos duplicados.


Ejemplo con tipo compuesto (class Automovil):


static void Main(string[] args)
{
    var coches = new List<Automovil>
    {
        new Automovil { Marca = "Renault", Modelo = "Clio"  , Carburante = TipoCarburante.Gasolina, Color = Colors.Green },
        new Automovil { Marca = "Citroen", Modelo = "C3"    , Carburante = TipoCarburante.Diesel  , Color = Colors.Gray  },
        new Automovil { Marca = "Renault", Modelo = "Laguna", Carburante = TipoCarburante.Diesel  , Color = Colors.Black },
        new Automovil { Marca = "Renault", Modelo = "Megane", Carburante = TipoCarburante.Gasolina, Color = Colors.Blue  },
        new Automovil { Marca = "Citroen", Modelo = "C4"    , Carburante = TipoCarburante.Gasolina, Color = Colors.Black }
    };
 
    var coches2 = new List<Automovil>
    {
        new Automovil { Marca = "Renault", Modelo = "Clio" , Carburante = TipoCarburante.Gasolina, Color = Colors.Green },
        new Automovil { Marca = "Seat"   , Modelo = "Ibiza", Carburante = TipoCarburante.Diesel  , Color = Colors.Gray  },
        new Automovil { Marca = "Fian"   , Modelo = "Punto", Carburante = TipoCarburante.Diesel  , Color = Colors.Black },
        new Automovil { Marca = "Citroen", Modelo = "C4"   , Carburante = TipoCarburante.Gasolina, Color = Colors.Coral }
    };
 
    var unionCoches = coches.Union(coches2, new AutomovilEqualityComparer());
    var unionCochesOrdenados = unionCoches.OrderBy(c => c.Marca).ThenBy(c => c.Modelo).ToList();
 
 
    Console.WriteLine("El resultado de la concatenación es:");
 
    foreach (var coche in unionCochesOrdenados)
    {
        Console.WriteLine("{0, -10} - {1, -6} - {2}", coche.Marca, coche.Modelo, coche.Color.ToString());
    }
 
 
 
    Console.Read();
}


Esta es la definición del IEqualityComparer<Automovil>:


public class AutomovilEqualityComparer : IEqualityComparer<Automovil>
{
 
    #region IEqualityComparer Members
 
    public bool Equals(Automovil x, Automovil y)
    {
        return x.Marca  == y.Marca &&
                x.Modelo == y.Modelo;
    }
 
    public int GetHashCode(Automovil obj)
    {
        return obj.Marca.GetHashCode() +
                obj.Modelo.GetHashCode();
    }
 
    #endregion
}

Resultado:















Como podemos observar hemos utilizado un comparador, AutomovilEqualityComparer. En caso de haber utilizado la primera sobrecarga, el resultado hubiera sido el mismo que si hubiéramos utilizado el operador Concat, ya que como hemos dicho en otras ocasiones, el comparer por defecto en las clases compara referencias, y por supuesto todas son diferentes.


El resultado es el mismo que el del operador Union de SQL.




Operador Intersect

Este operador produce una secuencia de salida con los ítems que son comunes en ambas colecciones.

Este operador también contiene una segunda sobrecarga con su comparador, para hacer un uso personalizado del mismo.


Estas son sus firmas:


public static IEnumerable<TSource> Intersect<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second) 
public static IEnumerable<TSource> Intersect<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer) 


Vamos con el ejemplo de datos simple (struct de tipo int):


static void Main(string[] args)
{
    var numeros1 = Enumerable.Range(1, 5).ToList(); /// 1, 2, 3, 4, 5
    var numeros2 = Enumerable.Range(3, 5).ToList(); /// 3, 4, 5, 6, 7
 
    var intersectNumeros = numeros1.Intersect(numeros2).OrderBy(n => n).ToList();
 
    intersectNumeros.ForEach(n => Console.WriteLine("  {0}", n));
 
    Console.Read();
}

Resultado:














El resultado está compuesto por los datos comunes de las dos colecciones.


Ejemplo con tipo compuesto (class Automovil):


static void Main(string[] args)
{
    var coches = new List<Automovil>
    {
        new Automovil { Marca = "Renault", Modelo = "Clio"  , Carburante = TipoCarburante.Gasolina, Color = Colors.Green },
        new Automovil { Marca = "Citroen", Modelo = "C3"    , Carburante = TipoCarburante.Diesel  , Color = Colors.Gray  },
        new Automovil { Marca = "Renault", Modelo = "Laguna", Carburante = TipoCarburante.Diesel  , Color = Colors.Black },
        new Automovil { Marca = "Renault", Modelo = "Megane", Carburante = TipoCarburante.Gasolina, Color = Colors.Blue  },
        new Automovil { Marca = "Citroen", Modelo = "C4"    , Carburante = TipoCarburante.Gasolina, Color = Colors.Black }
    };
 
    var coches2 = new List<Automovil>
    {
        new Automovil { Marca = "Renault", Modelo = "Clio" , Carburante = TipoCarburante.Gasolina, Color = Colors.Green },
        new Automovil { Marca = "Seat"   , Modelo = "Ibiza", Carburante = TipoCarburante.Diesel  , Color = Colors.Gray  },
        new Automovil { Marca = "Fian"   , Modelo = "Punto", Carburante = TipoCarburante.Diesel  , Color = Colors.Black },
        new Automovil { Marca = "Citroen", Modelo = "C4"   , Carburante = TipoCarburante.Gasolina, Color = Colors.Coral }
    };
 
    var intersectCoches = coches.Intersect(coches2, new AutomovilEqualityComparer());
    var intersectCochesOrdenados = intersectCoches.OrderBy(c => c.Marca).ThenBy(c => c.Modelo).ToList();
 
 
    Console.WriteLine("El resultado de la concatenación es:");
 
    foreach (var coche in intersectCochesOrdenados)
    {
        Console.WriteLine("{0, -10} - {1, -6} - {2}", coche.Marca, coche.Modelo, coche.Color.ToString());
    }
 
 
 
    Console.Read();
}


Resultado:













Como en el caso anterior con los números simples, el resultado está compuesto por los datos que concuerdan en cada una de las colecciones según el criterio de comparación de su IEqualityComparer<Automovil>.


Operador Except

El operador Except, produce una secuencia de resultado con los elementos de la primera secuencia, que no se encuentran dentro de la segunda secuencia.

Este operador también contiene una segunda sobrecarga con su comparador, para hacer un uso personalizado del mismo.


Estas son sus firmas:


public static IEnumerable<TSource> Except<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second) 
public static IEnumerable<TSource> Except<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer) 


Vamos con el ejemplo de datos simple (struct de tipo int):


static void Main(string[] args)
{
    var numeros1 = Enumerable.Range(1, 5).ToList(); /// 1, 2, 3, 4, 5
    var numeros2 = Enumerable.Range(3, 5).ToList(); /// 3, 4, 5, 6, 7
 
    var exceptNumeros = numeros1.Except(numeros2).OrderBy(n => n).ToList();
 
    exceptNumeros.ForEach(n => Console.WriteLine("  {0}", n));
 
    Console.Read();
}


Resultado:












El resultado está compuesto por los elementos de la primera colección de números que no aparecen en la segunda.


Ejemplo con tipo compuesto (class Automovil):


static void Main(string[] args)
{
    var coches = new List<Automovil>
    {
        new Automovil { Marca = "Renault", Modelo = "Clio"  , Carburante = TipoCarburante.Gasolina, Color = Colors.Green },
        new Automovil { Marca = "Citroen", Modelo = "C3"    , Carburante = TipoCarburante.Diesel  , Color = Colors.Gray  },
        new Automovil { Marca = "Renault", Modelo = "Laguna", Carburante = TipoCarburante.Diesel  , Color = Colors.Black },
        new Automovil { Marca = "Renault", Modelo = "Megane", Carburante = TipoCarburante.Gasolina, Color = Colors.Blue  },
        new Automovil { Marca = "Citroen", Modelo = "C4"    , Carburante = TipoCarburante.Gasolina, Color = Colors.Black }
    };
 
    var coches2 = new List<Automovil>
    {
        new Automovil { Marca = "Renault", Modelo = "Clio" , Carburante = TipoCarburante.Gasolina, Color = Colors.Green },
        new Automovil { Marca = "Seat"   , Modelo = "Ibiza", Carburante = TipoCarburante.Diesel  , Color = Colors.Gray  },
        new Automovil { Marca = "Fian"   , Modelo = "Punto", Carburante = TipoCarburante.Diesel  , Color = Colors.Black },
        new Automovil { Marca = "Citroen", Modelo = "C4"   , Carburante = TipoCarburante.Gasolina, Color = Colors.Coral }
    };
 
    var exceptCoches = coches.Except(coches2, new AutomovilEqualityComparer());
    var exceptCochesOrdenados = exceptCoches.OrderBy(c => c.Marca).ThenBy(c => c.Modelo).ToList();
 
 
    Console.WriteLine("El resultado de la excepción es:");
 
    foreach (var coche in exceptCochesOrdenados)
    {
        Console.WriteLine("{0, -10} - {1, -6} - {2}", coche.Marca, coche.Modelo, coche.Color.ToString());
    }
 
 
 
    Console.Read();
}


Resultado:













Al igual que con el caso simple, tenemos como resultado, los automóviles del primer grupo que no están en el segundo, siempre baja el criterio marcado por IEqualityComparer<Automovil>.



Operador Distinct

El operador Distinct, es el caso especial dentro de los ‘Set Operators’, ya que solo opera sobre una colección de datos. Realiza una distinción para dar como resultado una secuencia con los distintos elementos de la colección.

Este operador también contiene una segunda sobrecarga con su comparador, para hacer un uso personalizado del mismo.


Estas son sus firmas:


public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source) 
public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer) 


Vamos con el ejemplo de datos simple (struct de tipo int):


static void Main(string[] args)
{
    var numeros1 = new int[] { 0, 1, 1, 1} ;
    var distinctNumeros = numeros1.Distinct().ToList();
 
    distinctNumeros.ForEach(n => Console.WriteLine("  {0}", n));
 
    Console.Read();
}

Resultado:











El resultado ha eliminado toda la redundancia de elementos ‘1’, y solo ha dejado los diferentes.


Ejemplo con tipo compuesto (class Automovil):


static void Main(string[] args)
{
    var coches = new List<Automovil>
    {
        new Automovil { Marca = "Renault", Modelo = "Clio"  , Carburante = TipoCarburante.Gasolina, Color = Colors.Green },
        new Automovil { Marca = "Citroen", Modelo = "C3"    , Carburante = TipoCarburante.Diesel  , Color = Colors.Gray  },
        new Automovil { Marca = "Renault", Modelo = "Laguna", Carburante = TipoCarburante.Diesel  , Color = Colors.Black },
        new Automovil { Marca = "Renault", Modelo = "Megane", Carburante = TipoCarburante.Gasolina, Color = Colors.Blue  },
        new Automovil { Marca = "Citroen", Modelo = "C4"    , Carburante = TipoCarburante.Gasolina, Color = Colors.Black },
        new Automovil { Marca = "Citroen", Modelo = "C4"    , Carburante = TipoCarburante.Gasolina, Color = Colors.White },
        new Automovil { Marca = "Citroen", Modelo = "C4"    , Carburante = TipoCarburante.Gasolina, Color = Colors.Violet },
        new Automovil { Marca = "Citroen", Modelo = "C4"    , Carburante = TipoCarburante.Gasolina, Color = Colors.Yellow }
    };
 
 
 
    var distinctCoches = coches.Distinct(new AutomovilEqualityComparer());
 
    Console.WriteLine("El resultado de los diferentes es:");
 
    foreach (var coche in distinctCoches)
    {
        Console.WriteLine("{0, -10} - {1, -6} - {2}", coche.Marca, coche.Modelo, coche.Color.ToString());
    }
 
 
 
    Console.Read();
}

Resultado:














El resultado ha sido el conjunto de los diferentes automóviles de la colección coches, según el criterio de nuestros IEqualityComparer<Automovil>.