lunes, 20 de abril de 2015

Quantifiers Operators, All, Any y Contains







Los Quantifier Operators, son un grupo bastante especial de métodos extensores que devuelven un objeto de tipo bool.

 Este tipo de métodos son en ocasiones obviados, normalmente por su desconocimiento y sustituidos por la unión del uso del método Where() + Count().

Este suele ser un error común de uso, ya que los Quantifier Operators están optimizados para este fin, y la ganancia de rendimiento con su uso es más que evidente.

Más adelante haremos hincapié en ello con unos ejemplos que expongan este manifiesto.











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




Operador All

El operador All, comprueba que todos los elementos de una secuencia, cumplan un determinado criterio. Este criterio se especifica mediante el mismo tipo que el operador Where, y no es otro que Func<TSource, bool>. Así quedaría la firma:

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


Vamos a por un ejemplo completo:

static void Main(string[] args)
{
    string[] nombres = { "Luis", "Pablo", "Susana", "María" };
 
    Console.WriteLine("¿ Empiezan todos por a ?{0}", Environment.NewLine);
 
    bool empiezanTodosPorA = nombres.All(n => n.StartsWith("a"));
 
    string respuesta = empiezanTodosPorA ? "SI" : "NO";
 
    Console.WriteLine(" - {0}", respuesta);
 
    Console.Read();
}


Vamos a ver el resultado:

















Operador Any

El operador Any, a diferencia del operador All, contiene 2 sobrecargas. La primera de ellas, en la cual no pasaremos ningún parámetro, simplemente comprueba que una secuencia no esté vacía, ósea que contenga algún elemento. La segunda sobrecarga es muy similar a la de All, y recibe el mismo tipo de parámetro Func<TSource, bool> , y verifica que algún elemento de la secuencia cumpla con este criterio:

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

Vamos con un ejemplo completo:

static void Main(string[] args)
{
    string[] nombres = { "Luis", "Pablo", "Susana", "María" };
 
    Console.WriteLine("¿ Está vacía ?{0}", Environment.NewLine);
    bool estaVacia = !nombres.Any();
    string respuestaVacia = estaVacia ? "SI" : "NO";
    Console.WriteLine(" - {0}", respuestaVacia);
 
    Console.WriteLine("{0}", Environment.NewLine);
 
    Console.WriteLine("¿ Empieza alguno por P ?{0}", Environment.NewLine);
    bool empiezaAlgunoPorA = nombres.Any(n => n.ToUpper().StartsWith("P"));
    string respuestaEmpieza = empiezaAlgunoPorA ? "SI" : "NO";
    Console.WriteLine(" - {0}", respuestaEmpieza);
 
    Console.Read();
}


Con el siguiente resultado:


















Operador Contains

El operador Contains, sirve para comprobar si la secuencia contiene un elemento equivalente al objeto facilitado por parámetros. Utilizo la palabra equivalente y no igual, porque este operador como otros que hemos estudiado anteriormente tiene una sobrecarga en la que acepta un IEqualityComparer<T> en el que podemos describir esta equivalencia.


Vamos con las firmas de los métodos extensores:


public static bool Comparer<TSource>(this IEnumerable<TSource> source, TSource value)
public static bool Comparer<TSource>(this IEnumerable<TSource> source, TSource value, IEqualityComparer<TSource> comparer)


Ejemplo de la primera sobrecarga:

static void Main(string[] args)
{
    string[] nombres = { "Luis", "Pablo", "Susana", "María" };
 
    Console.WriteLine("¿ Está Susana en 'nombres' ?{0}", Environment.NewLine);
    bool estaVacia = nombres.Contains("Susana");
    string respuestaVacia = estaVacia ? "SI" : "NO";
    Console.WriteLine(" - {0}", respuestaVacia);
 
    Console.Read();
}

Resultado :












La segunda sobrecarga del operador Contais, acepta un parámetro adicional de tipo IEqualityComparer<T>. El funcionamiento es exactamente el mismo, al de las entregas anteriores de GroupBy y Join, y no es otro que el diseñar una política de igualdad para un tipo determinado.


Para hacer el ejemplo utilizaremos una clase de las que ya hemos utilizado en otras entradas, la clase Automovil:


public class Automovil
{
    public string           Marca       { get; set; }
    public string           Modelo      { get; set; }
    public TipoCarburante   Carburante  { get; set; }
    public Color            Color       { get; set; }
}

Y crearemos un IEqualityComparer<Automovil>, para definir nuestra propia forma de igualdad. Recordad que podríamos tener varías formas, con diferentes IEqualityComparer<Automovil> en nuestro proyecto, que definieran diferentes políticas de igualdad según cada necesidad.


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 cocheAComparar = new Automovil
    {
        Marca      = "Renault",
        Modelo     = "Laguna",
        Carburante = TipoCarburante.Diesel,
        Color      = Colors.BlueViolet
    };
 
    Console.WriteLine("¿ Está 'cocheAComparar' dentro de coches ?{0}", Environment.NewLine);
 
    bool resultado = coches.Contains(cocheAComparar, new AutomovilEqualityComparer());
 
    string respuesta = resultado ? "SI" : "NO";
    Console.WriteLine(" - {0}", respuesta);
 
 
    Console.Read();
}

Resultado:












Resaltar, que de no haber indicado nuestro IEqualityComparer<Automovil>, el resultado hubiera sido negativo, ya que hubiera utilizado la estrategia por defecto, y en el Framework para las clases no es otra que comparar por referencias, y nuestro cocheAComparar no apunta a la misma dirección de memoria que ninguno de los elementos que forman la colección coches.

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 cocheAComparar = new Automovil
    {
        Marca      = "Renault",
        Modelo     = "Laguna",
        Carburante = TipoCarburante.Diesel,
        Color      = Colors.BlueViolet
    };
 
    Console.WriteLine("¿ Está 'cocheAComparar' dentro de coches ?{0}", Environment.NewLine);
 
    bool resultado = coches.Contains(cocheAComparar);
 
    string respuesta = resultado ? "SI" : "NO";
    Console.WriteLine(" - {0}", respuesta);
 
 
    Console.Read();
}

Exactamente igual que el ejemplo anterior, pero sin el parámetro comparador dentro de la llamada a la cláusula Contais:


Resultado:













Para rizar un poco más el rizo, y demostrar el tema de las referencias, indicar que si hubiéramos instanciado nuestro cocheAComparar de la manera siguiente, la comparación si hubiera resultado positiva:

var cocheAComparar = coches.First();

Pongo mucho ímpetu en el uso de IEqualityComparer<T> ya que puede ser verdaderamente práctico dentro de todo el universo LinQ




Uso de Where() + Count()

Tengo que reconocer que durante mucho tiempo he realizado prácticas mediante la combinación de estos dos operadores para suplir el uso de los que hoy nos afectan. Esta práctica la realizaba principalmente por desconocimiento de los mismos. Con lo que hacía cosas como estas:

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 cochesNegros = coches.Where(c => c.Color == Colors.Black).ToList();
 
    if (cochesNegros.Count > 0)  /// Any()
    {
        Console.WriteLine("Los siguientes coches son negros :{0}", Environment.NewLine);
        foreach (var cocheNegro in cochesNegros)
        {
            Console.WriteLine("Marca: {0} - Modelo: {1} ...", cocheNegro.Marca, cocheNegro.Modelo);
        }
    }
 
    var cochesCitroen = coches.Where(c => c.Marca.Equals("Citroen", StringComparison.CurrentCultureIgnoreCase)).ToList();
 
    if (cochesCitroen.Count == coches.Count) /// All()
    {
        Console.WriteLine("Todos los coches son de la marca Citroen");
    }
    else
    {
        Console.WriteLine("Hay coches que NO son de la marca Citroen");
    }
 
    Console.Read();
}


Apuntar de nuevo, que este procedimiento no está recomendado, ya que los operadores Any y All, están optimizados para este tipo de casuísticas.