Orden en la sala

Orden en la sala
Cuando queremos ordenar una lista sin que esta implemente la interfaz Comparator, podemos hacerlo también mediante programación funcional. Para comenzar, usaremos el objeto Persona y la colección de personas que tenemos en el post sobre Colecciones en Java.
En primer lugar usaremos el ordenamiento estándar con la clase Persona, en la que la clase implementa la interfaz Comparator de modo que debe existir un método llamado compareTo el cual podemos usar para ordenar por defecto.
Al crear un ArrayList personas=new ArrayList();, este tiene un método llamado sort(Comparator comparator) y por tanto necesitamos un objeto de este tipo con el que ordenar los elementos de la lista, de modo que tenemos que crear una clase Comparator

public class ComparatorByName implements Comparator{

    @Override
    public int compare(Persona o1, Persona o2) {
        return o1.compareTo(o2);
    }
    
}

o podemos instanciar el objeto desde el argumento del método sort, generando el bloque de código con el método compareTo

ArrayList personas=new ArrayList();
        
        personas.sort(new Comparator() {
            @Override
            public int compare(Persona o1, Persona o2) {
                return o1.compareTo(o2);
            }
        });

Con programación funcional, todo se vuelve más fácil, en el siguiente ejemplo creamos dos objetos Comparator a los que le pasamos dos argumentos de los dos objetos a comparar que coincide con la firma del método de comparación del objeto original, pudiendo incluso aprovechar los métodos compareTo de la clase String para crear nuestro comparador personalizado.

Comparator comparatorEstandar= (pa, pb)-> pa.compareTo(pb);
Comparator comparatorByName= (pa, pb)-> pa.getNombre().compareTo(pb.getNombre());
personas.sort(comparatorByName);
personas.sort(comparatorEstandar);

Com ya vimos en el post Colecciones, mediante stream podemos ordenar con:

  • Método de la clase
  • Añadiendo un objeto Comparator creado mediante programación funcional o estándar
  • Añadiendo a la expresión lambda dos parámetros y la comparación.
personas.stream().
            sorted().
            forEach(p-> System.out.println(p.getFullName()));
        
        personas.stream().
            sorted(comparatorByName).
            forEach(p-> System.out.println(p.getFullName()));
        
        personas.stream().
            sorted((pa,pb)-> pa.getNombre().compareTo(pb.getNombre())).
            forEach(p-> System.out.println(p.getFullName()));

Por último para rizar el rizo… y ¿si queremos ordenar por varios campos, por ejemplo primero por apellido y después por edad? esto es posible con el método thenComparing para añadir un nuevo orden con otro comparador anidado.

        
        Comparator<Persona> comparatorByNameAndAge= comparatorByName.thenComparing((p0,p1)->p0.compareTo(p1));.
            forEach(p-> System.out.println(p.getFullName()));

Colecciones II

Colecciones II
Continuando con las colecciones, vamos a ver los mismo métodos y procedimientos usados en el post anterior, pero esta vez exclusivamente con Java.

Nuestra clase Persona en Java quedaría del siguiente modo:

public class Persona implements Comparable{
    public Persona(String _name,
            String _apellidos,
            boolean _isMan,
            LocalDate fechaNacimiento,
            String _lugarNacimiento){
        nombre=_name;
        apellidos=_apellidos;
        isMan =_isMan;
        nacimiento=fechaNacimiento;
        lugarNacimiento=_lugarNacimiento;
    }

    String nombre;
    String apellidos;
    LocalDate nacimiento;
    String lugarNacimiento;
    Boolean isMan;

    public String getNombre() {
        return nombre;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    public String getApellidos() {
        return apellidos;
    }

    public void setApellidos(String apellidos) {
        this.apellidos = apellidos;
    }

    public LocalDate getNacimiento() {
        return nacimiento;
    }

    public void setNacimiento(LocalDate nacimiento) {
        this.nacimiento = nacimiento;
    }

    public String getLugarNacimiento() {
        return lugarNacimiento;
    }

    public void setLugarNacimiento(String lugarNacimiento) {
        this.lugarNacimiento = lugarNacimiento;
    }

    public Boolean getIsMan() {
        return isMan;
    }

    public void setIsMan(Boolean isMan) {
        this.isMan = isMan;
    }

    public int getEdad(){
        LocalDate now= LocalDate.now();
        return now.getYear() -
                nacimiento.getYear() +
                Integer.compare(now.getDayOfYear(), nacimiento.getDayOfYear());
    }

    @Override
    public String toString() {
        return "Persona{" + "nombre=" + nombre + ", apellidos=" + apellidos + ", edad= " + getEdad() + "}";
    }

    public boolean isFrom(String city){
        return lugarNacimiento.equals(city);
    }

    public String getFullName(){
        return String.format("%s, %s", getApellidos(), getNombre());
    }

    @Override
    public int compareTo(Persona o) {
        return this.apellidos.compareTo(o.apellidos);
    }
}

añadimos los elementos a la colección de igual modo, esta vez usando para la fecha la clase LocalDate

 static void addPersonas(){
        personas.add(new Persona("Joaquin", "Martinez Rus", true,LocalDate.of(1969, 2, 20), "Linares"));
      personas.add(new Persona("Vicky", "Diez Calatayud", false,LocalDate.of(1996, 4, 19), "Calatayud"));
      personas.add(new Persona("Ana Isabel", "Martinez Baloo", false,LocalDate.of(1998, 10, 20), "Cordoba"));
      personas.add(new Persona("Victoria", "Martinez Nurse", false,LocalDate.of(1992, 1, 21), "Cordoba"));
      personas.add(new Persona("Ana", "Rus Maria", false,LocalDate.of(1961, 11, 3), "Linares"));
      personas.add(new Persona("Manuel", "Bonillo Contador", true,LocalDate.of(1978, 12, 26), "Cordoba"));
      personas.add(new Persona("Jose Antonio", "Martinez Arcos", true,LocalDate.of(1974, 9, 4), "Zaragoza"));
      personas.add(new Persona("Vicente", "Rodriguez Iglesias", true,LocalDate.of(1981, 11, 8), "Cordoba"));
      personas.add(new Persona("Alfonso", "Perez Judicial", true,LocalDate.of(2000, 5, 5), "Linares"));
      personas.add(new Persona("Ana", "Martinez Maestre", false,LocalDate.of(2004, 3, 4), "Linares"));
      personas.add(new Persona("Magdalena", "Fuentes Cruz", false,LocalDate.of(2002, 7, 11), "Jaen"));
      personas.add(new Persona("Jose", "Martinez Cintas", true,LocalDate.of(1981, 6, 21), "Linares"));
      personas.add(new Persona("Jose", "Aldea Morata", true,LocalDate.of(1994, 11, 22), "Calatayud"));
      personas.add(new Persona("Maria Isabel", "Diez Campieles", false,LocalDate.of(2005, 8, 1), "Calatayud"));
      personas.add(new Persona("Maria", "Martinez Adriá", false,LocalDate.of(2001, 10, 22), "Jaen"));
      personas.add(new Persona("Pedro José", "Garcia Civil", true,LocalDate.of(1980, 9, 15), "Cordoba"));
      personas.add(new Persona("Luis", "Jiloca Diez", true,LocalDate.of(1995, 6, 15), "Zaragoza"));
      personas.add(new Persona("Juan", "Pintor Escultor", true,LocalDate.of(2000, 10, 22), "Jaen"));
      personas.add(new Persona("Juan", "Goran Esteban", true,LocalDate.of(1974, 7, 14), "Jaen"));
      personas.add(new Persona("Andrea", "Jiloca Diez", false,LocalDate.of(1999, 9, 21), "Calatayud"));
      personas.add(new Persona("Daniel", "Ceular Flower", true,LocalDate.of(1975, 2, 4), "Cordoba"));
    }

y pasamos al primer ejemplo aunque lógicamente solo mostraré el método tradicional y la expresión lambda, las cuales fueron incluidas con Java 8.
Ejemplo 1. ¿cual es el promedio de edad de todas las personas nacidas en Calatayud?
Java tiene algunas variaciones con respecto a C#, de modo que para crear una expresión lambda sobre una colección, debemos crear un stream (o iteración) y sobre este con filter hacemos la función where en C# y por último con mapToDouble, convertimos o mejor dicho creamos un stream de objetos tipo Double a los cuales le calculamos el promedio.

 
static void getAverageAge(String lugarNacimiento){
        double suma=0;
        double count=0;
        for (Persona persona : personas) {
            if (persona.getLugarNacimiento().equals(lugarNacimiento)) {
                suma += persona.getEdad();
                count ++;
            }
        }
        double promedio = suma / count;
        System.out.printf("Las personas nacidas en %s tienen un promedio de edad de %s\n", lugarNacimiento, promedio);
    }
static void getStreamAverageAge(String lugarNacimiento){
        OptionalDouble promedio= personas.
                       stream().
                       filter(k->k.isFrom(lugarNacimiento)).
                       mapToDouble(e->e.getEdad()).
                       average();
        System.out.printf("Las personas nacidas en %s tienen un promedio de edad de %s\n", lugarNacimiento, promedio.getAsDouble());
    }

Ejemplo 2. Queremos listar todos los mayores de edad por orden alfabético de los apellidos. En este ejemplo, debemos implementar la interface Comparator incluir el método compareTo y con esto mostramos el código

     static void printAdults(){
         personas.sort(new Comparator() {
             @Override
             public int compare(Persona o1, Persona o2) {
                 return o1.apellidos.compareTo(o2.apellidos);
             }
         });
         for (Persona persona : personas) {
              if (persona.getEdad()>17)
                {
                    System.out.println(persona.getFullName());
                }
         }
     }
     
     static void printStreamAdults(){
         personas.stream().
                 filter(p->p.getEdad()>17).
                 sorted().
                 forEach(p-> System.out.println(p.getFullName()));
     }

y el resultado que debe ser exactemente igual.
coleccion3

Colecciones I

Colecciones I

Hoy vamos a ver como aprovecharnos de herramientas que nos permiten usar un código más limpio y entendible en cuanto a colecciones se refiere y para ello veremos como hacerlo en este post para colecciones con expresiones lambda y Linq de .NET con C# y en el siguiente post, haremos esto mismo pero con Java.

En primer lugar crearemos un clase  Persona, dándole algunas propiedades como nombre, apellidos, nacimiento, un valor booleano para conocer el género, fecha de nacimiento y lugar de nacimiento, además le añadimos una propiedad de solo lectura que calcule la edad, toString, una para obtener el nombre completo y otro método booleano para saber si es nacido en un lugar concreto.

namespace ColeccionesDemo
{
    public class Persona
    {
        public Persona(string _name,
            string _surname,
            bool _isMan,
            DateTime _birthDate,
            string _birthPlace)
        {
            Name = _name;
            SurName = _surname;
            IsMan = _isMan;
            BirthDate = _birthDate;
            BirthPlace = _birthPlace;
        }

        public string Name { get; set; }
        public string SurName { get; set; }
        public DateTime BirthDate { get; set; }
        public string BirthPlace { get; set; }
        public bool IsMan { get; set; }
        public int Age
        {
            get
            {
                DateTime now = DateTime.Now;
                return now.Year - BirthDate.Year + (now.DayOfYear Nombre: {0}, Apellidos: {1}, Edad: {2}, De: {3}", Name, SurName, Age, BirthPlace); }
        }
        public string FullName
        {
            get { return string.Format("{0} {1}", Name, SurName); }
        }
        public override bool Equals(object obj)
        {
            var persona = obj as Persona;
            return persona != null &&
                   Name == persona.Name &&
                   SurName == persona.SurName;
        }
    }
}

Una vez creada la clase persona, para C# vamos a crear un objeto que almacene esta clase con una colección List la cual implementa la interfaz IEnumerable y a la cual le añadiremos algunos individuos ficticios.

      static List personas = new List();

        static void addPersonas()
        {
            personas.Add(new Persona("Joaquin", "Martinez Rus", true, new DateTime(1969, 2, 20), "Linares"));
            personas.Add(new Persona("Vicky", "Diez Calatayud", false, new DateTime(1996, 4, 19), "Calatayud"));
            personas.Add(new Persona("Ana Isabel", "Martinez Baloo", false, new DateTime(1998, 10, 20), "Cordoba"));
            personas.Add(new Persona("Victoria", "Martinez Nurse", false, new DateTime(1992, 1, 21), "Cordoba"));
            personas.Add(new Persona("Ana", "Rus Maria", false, new DateTime(1961, 11, 3), "Linares"));
            personas.Add(new Persona("Manuel", "Bonillo Contador", true, new DateTime(1978, 12, 26), "Cordoba"));
            personas.Add(new Persona("Jose Antonio", "Martinez Arcos", true, new DateTime(1974, 9, 4), "Zaragoza"));
            personas.Add(new Persona("Vicente", "Rodriguez Iglesias", true, new DateTime(1981, 11, 8), "Cordoba"));
            personas.Add(new Persona("Alfonso", "Perez Judicial", true, new DateTime(2000, 5, 5), "Linares"));
            personas.Add(new Persona("Ana", "Martinez Maestre", false, new DateTime(2004, 3, 4), "Linares"));
            personas.Add(new Persona("Magdalena", "Fuentes Cruz", false, new DateTime(2002, 7, 11), "Jaen"));
            personas.Add(new Persona("Jose", "Martinez Cintas", true, new DateTime(1981, 6, 21), "Linares"));
            personas.Add(new Persona("Jose", "Aldea Morata", true, new DateTime(1994, 11, 22), "Calatayud"));
            personas.Add(new Persona("Maria Isabel", "Diez Campieles", false, new DateTime(2005, 8, 1), "Calatayud"));
            personas.Add(new Persona("Maria", "Bella Adriá", false, new DateTime(2001, 10, 22), "Jaen"));
            personas.Add(new Persona("Pedro José", "Garcia Civil", true, new DateTime(1980, 9, 15), "Cordoba"));
            personas.Add(new Persona("Luis", "Jiloca Diez", true, new DateTime(1995, 6, 15), "Zaragoza"));
            personas.Add(new Persona("Juan", "Pintor Escultor", true, new DateTime(2000, 10, 22), "Jaen"));
            personas.Add(new Persona("Juan", "Goran Esteban", true, new DateTime(1974, 7, 14), "Jaen"));
            personas.Add(new Persona("Andrea", "Jiloca Diez", false, new DateTime(1999, 9, 21), "Calatayud"));
            personas.Add(new Persona("Daniel", "Ceular Flower", true, new DateTime(1975, 2, 4), "Cordoba"));<span id="mce_SELREST_start" style="overflow:hidden;line-height:0;"></span>
        }

Una vez añadidos, vamos a pedirle a nuestra colección de personas unas cuantos datos con criterios concretos.
Ejemplo 1. ¿cual es el promedio de edad de todas las personas nacidas en Calatayud?
Vamos a crear tres métodos, uno paso a paso, calcAverageAge(string _birthPlace), es decir inicializamos variables, iteramos por la colección, comprobamos la condición, calculamos e imprimimos. El segundo método mediante programación funcional calcAverageAgeFunctionalProgramming(string _birthPlace), escribiremos una línea para calcular el promedio y otra para imprimirlo (aunque lo podía haber hecho en la misma línea), por último otro modo sería con Linq igual de vistoso que el anterior con el método calcAverageAgeLinq(string _birthPlace)

static void calcAverageAge(string _birthPlace)
        {
            double suma = 0;
            double count = 0;
            double average = 0;
            foreach (var persona in personas)
            {
                if (persona.IsFrom(_birthPlace))
                {
                    suma += persona.Age;
                    count++;
                }
            }
            average = suma / count;
            Console.WriteLine(string.Format("Las personas nacidas en {0} tienen un promedio de edad de {1} ", _birthPlace, average));

        }

        static void calcAverageAgeFunctionalProgramming(string _birthPlace)
        {
            var average= personas.Where(n => n.IsFrom(_birthPlace)).Average(p => p.Age);
            Console.WriteLine(string.Format("Programación funcional\nLas personas nacidas en {0} tienen un promedio de edad de {1} ", _birthPlace, average));
        }

        static void calcAverageAgeLinq(string _birthPlace)
        {
            var average = (from u in personas
                         where u.IsFrom(_birthPlace)
                         select u).Average(p => p.Age);
            Console.WriteLine(string.Format("Programación funcional\nLas personas nacidas en {0} tienen un promedio de edad de {1} ", _birthPlace, average));
        }

Ejemplo 2. Queremos listar todos los mayores de edad por orden alfabético de los apellidos.
Con el método estándar printAdults() y como queremos ordenar por los apellidos hemos tenido que incluir la interfaz IComparable que implementa el método CompareTo(Persona other) efectuando la comparación de los apellidos y ejecutando el método Sort de la clase List. En el siguiente método con programación funcional printAdultsFunctionalProgramming(), en una sola línea efectuamos todas las operaciones sin hacer referencia a ninguna interfaz IComparable y por último el mismo método basado en Linq printAdultsLinq(). Este tipo de expresiones en una línea, las llamamos expresiones lambda.

static void printAdults()
        {
            personas.Sort();
            foreach (var persona in personas)
            {
                if (persona.Age>17)
                {
                    Console.WriteLine(persona.FullName);
                }
            }
        }

        static void printAdultsFunctionalProgramming()
        {
            Console.WriteLine("******************************************");
            Console.WriteLine("Mayores de edad con programación funcional");
            Console.WriteLine("******************************************");
            personas.Where(p => p.Age > 17).
                OrderBy(p=>p.SurName).
                ToList().
                ForEach(p => Console.WriteLine(p.FullName));
        }

        static void printAdultsLinq()
        {
            Console.WriteLine("************************");
            Console.WriteLine("Mayores de edad con Linq");
            Console.WriteLine("************************");
            (from persona in personas
                      where persona.Age > 17
                      orderby persona.SurName
                      select persona).ToList().ForEach(p => Console.WriteLine(p.FullName));
        }

Resultado, el mismo, pero a mi entender, claridad en el código máxima con programación funcional. En el siguiente post, haremos lo mismo pero con Java.
coleccion1coleccion2