miércoles, 6 de mayo de 2015

Partitions Operators, Take, Skip, TakeWhile y SkipWhile






El objetivo de los operadores de partición, consiste en restringir el número de elementos de una secuencia. Para ello creará 2 subsecuencias en memoria, en base a un parámetro int o un Func<T, bool>, según el tipo de operador. Una de estas dos subsecuencias, serán el resultado, y la elección, también se tendrá en cuenta según el operador utilizado. Para vislumbrar mejor estas líneas, vamos a ver cada uno de ellos con más detalle:












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



Operador Take

El operador Take, toma como parámetro un objeto de tipo int. Con este parámetro notificaremos el número de elementos que nos queremos quedar, empezando por el índice 0, ósea, por el primero.

En otras palabras, como su propia traducción dice, toma empezando desde el primer elemento, el número de elementos facilitado por el parámetro.


public static IEnumerable<TSource> Take<TSource>(this IEnumerable<TSource> source, int takeElements) 


Vamos con un ejemplo:

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 dosPrimerosCoches = coches.Take(2).ToList();
 
    foreach (var coche in dosPrimerosCoches)
    {
        Console.WriteLine("{0} - {1}", coche.Marca, coche.Modelo);
    }
 
    Console.Read();
}


Pues como se suele decir, una imagen vale más que mil palabras:






Como podemos apreciar en la imagen, se forman dos subgrupos, y el resultado para el operador Take(2), es el primer subconjunto.


Y para que no falte, ponemos también el resultado de la consola:
















Al igual que el operador Take, el operador Skip, posee un parámetro que de tipo int, pero en este caso construye su resultado desechando los el número de elementos indicado, empezando por el primero, por lo que nuestro resultado será el segundo subgrupo de elementos.


Dicho de otra manera y utilizando también su traducción al castellano, el operador Skip, salta el número de items del que le informa su parámetro, para construir su secuencia de resultado.



public static IEnumerable<TSource> Skip<TSource>(this IEnumerable<TSource> source, int takeElements) 


























Vamos a ver un ejemplo en código:


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 todosMenosDosPrimerosCoches = coches.Skip(2).ToList();
 
    foreach (var coche in todosMenosDosPrimerosCoches)
    {
        Console.WriteLine("{0} - {1}", coche.Marca, coche.Modelo);
    }
 
    Console.Read();
}


Ahí va el resultado:
















Operador TakeWhile

El operador TakeWhile, retorna elementos de una secuencia, mientras se cumpla la condición de su parámetro. Como en los operadores anteriores, también se construyen 2 subconjuntos virtuales, pero en este caso, no es un parámetro numérico el que decide el límite entre uno y otro, es un parámetro de tipo delegado genérico Func<TSource,bool> normalmente llamado predicado.


public static IEnumerable<TSource> TakeWhile<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) 


El operador TakeWhile, al igual que Select y Where, tiene una sobre carga que nos facilita el índice del elemento de la secuencia, con un parámetro de tipo Func<TSource, int, bool>.


public static IEnumerable<TSource> TakeWhile<TSource>(this IEnumerable<TSource> source, Func<TSource, int, bool> predicate) 


Voy a mantener el mismo ejemplo, para que nos valga como símil, y así poder recalcar las diferencias con los operadores precedentes.






















Vamos con el código:


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 cochesQueEmpiezanPorC = coches.TakeWhile(c => c.Modelo.StartsWith("c", StringComparison.CurrentCultureIgnoreCase)).ToList();
 
 
    foreach (var coche in cochesQueEmpiezanPorC)
    {
        Console.WriteLine("{0} - {1}", coche.Marca, coche.Modelo);
    }
 
    Console.Read();
}


El resultado es exactamente el mismo que el ejemplo de Take. La diferencia radica en que en el ejemplo de Take, el parámetro era de tipo int, en nuestro ejemplo un 2, siendo este el número que indica el límite para crear los dos subconjuntos. En el caso de TakeWhile, el límite lo marca, todos los elementos que su Modelo, empiece por “c”, por supuesto, empezando por el primero de ellos.

Al ser nuestro operado un caso especial de Take, el resultado por supuesto será el primer subconjunto.


Vamos con el resultado de la consola:
















Diferencias entre TakeWhile y Where

En muchas ocasiones, si aplicamos ambos operadores sobre la misma colección de objetos, el resultado es exactamente el mismo. También tenemos que señalar que tienen el mismo número de parámetros y del mismo tipo. Todo esto nos puede llevar a pensar que realizan el mismo trabajo y que es indiferente el uso de uno o de otro. Nada más allá.

La principal diferencia radica, en que el operador TakeWhile, comienza ‘la busqueda’ desde el primer elemento y va comprobando uno a uno cada elemento para ver si cumple con su criterio, en cuanto haya uno solo que no lo cumpla, realiza el corte del subconjunto y no sigue buscando más. Podríamos decir que solo busca los primeros que cumplan la condición. El operador Where, hace exactamente lo mismo, pero no se para en cuanto encuentra un elemento que no cumpla con el requisito, sigue comprobando todos los elementos de la secuencia.


En nuestro ejemplo:









TakeWhile, daría como resultado los 2 primeros coches, mientras que Where sumaría a esos dos el 4 coche.



Operador SkipWhile

Para resumir un poco y quitarnos de tanta literatura, este tiene el mismo comportamiento que TakeWhile, pero con las características de su homónimo sencillo Skip, ósea escogiendo el segundo subconjunto, ósea saltando los datos que cumplen con su criterio.


También tiene 2 sobrecargas como TakeWhile:


public static IEnumerable<TSource> SpkipWhile<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) 
public static IEnumerable<TSource> SpkipWhile<TSource>(this IEnumerable<TSource> source, Func<TSource, int, bool> predicate) 


Vamos a ver su ejemplo:


gruposYSubgrupos










































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 cochesQueNoEmpiezanPorC = coches.SkipWhile(c => c.Modelo.StartsWith("c", StringComparison.CurrentCultureIgnoreCase)).ToList();
 
    //coches.Where()
 
    foreach (var coche in cochesQueNoEmpiezanPorC)
    {
        Console.WriteLine("{0} - {1}", coche.Marca, coche.Modelo);
    }
 
    Console.Read();
}


Resultado: