15 dic 2020

Ordenación de colecciones y listas mediante las interfaces Comparable y Comparator en Java

Ordenación de colecciones mediante la interfaz Comparable

Es necesario implementar la interfaz Comparable que declara el método:

int compareTo(Object o);

Este método compara el objeto  sobre el que actúa con el objeto recibido como parámetro.
Este método tiene que devolver un valor negativo si éste objeto es menor que el recibido, 0 si son iguales o un valor positivo si es mayor, según el orden natural de los objetos, que en muchos casos habrá que especificar.
Por ejemplo, para una clase persona, podría interesar ordenar por dni, o por apellidos y nombre, o por nombres y apellidos, etc.

public class Persona implements Comparable {
    private String nombre;
    private String apellidos;
    private String dni;
    private Date fechaNacimiento;   

public int compareTo(Object o) {
        Persona persona = (Persona)o;       

        if(this.apellidos.compareToIgnoreCase(persona.apellidos) == 0) {           
            if(this.nombre.compareToIgnoreCase(persona.nombre) == 0) {
                return this.dni.compareTo(persona.dni);
            } else {
                return this.nombre.compareToIgnoreCase(persona.nombre);
            }
        } else {
            return this.apellidos.compareToIgnoreCase(persona.apellidos);
        }       

    }
}                          
// Compara por apellidos, nombre y dni

public int compareTo(Object o) {
        Persona persona = (Persona)o;       
        return this.dni.compareTo(persona.dni);
           
}                          
// Compara sólo por el dni



Una vez que tuviéramos una lista con datos, en este caso Personas, la ordenaríamos con la clase java.util.Collections, de la siguiente forma:

Collections.sort(lista);   


Este método puede ordenar cualquier colección que implemente el interface java.util.List, según el orden que indica el método comparteTo.


Ordenación de listas y colecciones mediante la interfaz Comparator

Ordenación de listas (vectores)

 Si lo que se quieren ordenar son listas (vectores), la forma más sencilla es mediante el métodos estáticos sort de la clase Arrays. Para listas de tipos atómicos se puede hacer directamente. Ejemplo:

int listaEnteros [] = {2, 1, 5, 6, 8, 3, 56, 42};

Arrays.sort(listaEnteros);

De igual forma se podría hacer si los elementos son de tipo referencia, como podrían ser String, StringBuilder o elementos de las clases Envoltorio (wrapper). Ejemplo:

String listaStrings [] = {“Ana”, “Pablo”, “Marcos”, “Rosa”, “Lucas”, “Sara”, “Marta”, “Pedro”};

Arrays.sort(listaStrings);


Por último, también se podría hacer para listas de objetos que tengan una clase comparador que implemente la interfaz comparator (y que por tanto defina cómo se comparan dos objetos de tipo Persona). Supongamos que la clase ComparadorPersona, implementa comparator. Se utilizaría así:

public class ComparadorPersona implements Comparator<Persona>{

    @Override

    public int compare(Persona o1, Persona o2) {

        return o1.getApellido1().compareToIgnoreCase(o2.getApellido1());        

    }

}

Persona listaPersonas [] = new Persona[50];

Arrays.sort(listaPersonas, new ComparadorPersona());  //compara por el primer apellido

En lugar de utilizar un comparador propio se podría utilizar el método comparing de la interfaz Comparator. De esta forma:

Comparator<Persona> cp= Comparator.comparing(Persona::getApellido2);

Arrays.sort(listaPersonas, cp);                      //compara por el segundo apellido

Se pueden incluso encadenar los campos por los que comparar. En el siguiente ejemplo se compara por el primer apellido, por el segundo y por el nombre.

Comparator<Persona> cp2= Comparator.comparing(Persona::getApellido1).              thenComparing(Persona::getApellido2).thenComparing(Persona::getNombre);

Arrays.sort(listaPersonas, cp2);

 

También se puede hacer que la comparación sea en orden descendente:

Comparator<Persona> cp3= Comparator.comparing(Persona::getApellido1).reversed();

Arrays.sort(listaPersonas, cp3); //compara por el primer apellido en orden descendente


Ordenación de colecciones

La ventaja que presenta la interfaz Comparator, es que permite ordenar colecciones por distintos campos en diferentes momentos. La solución es crear distintas clases que implementen la interfaz Comparator, cada una que ordene de forma distinta, para después poder usarlas cuando convenga. Después podremos utilizar Collections.sort(), pero pasándole como segundo argumento un comparador.

import java.util.Comparator;                        // CompFichaDni.java

public class CompFichaDni implements Comparator<Ficha>{

     @Override

    public int compare(Ficha o1, Ficha o2){

        return o1.getDni().compareTo(o2.getDni());

}

}


import java.util.Comparator;                        // CompFichaNota.java

public class CompFichaNota implements Comparator<Ficha>{

     @Override

    public int compare(Ficha o1, Ficha o2){

        return (o1.getNota()>o2.getNota() ? 1  : o1.getNota()==o2.getNota() ? 0 :-1);

}

}

 

Después se utilizaría de esta forma:

System.out.println("\n Ordenados por dni:");

 Collections.sort(lista, new CompFichaDni());

 …       

 System.out.println("\n Ordenados por nota:");

Collections.sort(lista, new CompFichaNota());


En lugar de crear una clase que implemente la interfaz, también se pueden crear comparadores directamente a partir de la interfaz y de los métodos de acceso. Supongamos una colección de personas que queremos ordenar por distintos atributos. Ejemplo:

Comparator<Persona> compPers1= Comparator.comparing(Persona::getApellido1);

Comparator<Persona> compPers2= Comparator.comparing(Persona::getNombre);

Comparator<Persona> compPers3= Comparator.comparing(Persona::getApellido1).                thenComparing(Persona::getApellido2).thenComparing(Persona::getNombre);

 

Collections.sort(coleccionPersonas, compPers1);   //compara por el primer apellido

Collections.sort(coleccionPersonas, compPers2);   //compara por el nombre

Collections.sort(coleccionPersonas, compPers3);  

//compara por el primer apellido, el segundo y el nombre

 

 

18 dic 2017

Las tildes y la ñ en la entrada y salida de datos en Java con NetBeans


Uno de los problemas iniciales con los que nos encontramos al leer cadenas de texto en Java, con, por ejemplo Scanner, es la utilización de tildes, ya que dependiendo de la configuración de las instrucciones de entrada, de los proyectos e incluso del propio Netbeans, los resultados pueden ser muy distintos.
Si leemos una cadena de texto que contenga tildes y la ñ, en un proyecto por defecto creado en Java con Netbeans en Windows, de esta forma:
import java.util.Scanner;
public class Tildes {
    public static void main(String[] args) {
      Scanner sc= new Scanner(System.in);
      String cadena;
      System.out.print("Entrada: ");
      cadena= sc.nextLine();
      System.out.printf("Salida: %s%n", cadena);
    }
}


Obtenemos este resultado:

Esto es debido a que la codificación que utiliza Netbeans por defecto para los proyectos, es UTF-8, mientras que la que utiliza Windows en español es, ISO-8859-1 (o el superconjunto Windows-1252), como podemos ver en las propiedades del proyecto:


Una posible solución es cambiar en el desplegable Encoding: y poner el valor Windows-1252, o bien ISO-8859-1:

Pero esto puede suponer un problema, si estamos leyendo o escribiendo ficheros de texto, porque nos los escribirá por defecto con la nueva codificación, como bien nos indica:
 

Una solución alternativa, es dejar el proyecto con la codificación UTF-8, por si leemos o escribimos ficheros de texto con esta codificación, y especificar la codificación en la entrada de datos de Scanner, de esta forma:


Scanner sc= new Scanner (System.in, "ISO-8859-1");


Con las dos soluciones obtendremos el resultado deseado:

Una solución más drástica sería cambiar la codificación por defecto (charset) para todos los proyectos Netbeans, esto lo haríamos en el fichero de configuración netbeans.conf, situado en el directorio raíz de nuestra instalación de Netbeans ( habitualmente c:\program files\Netbeans-xxx\). La línea:
netbeans_default_options="-J-Dfile.encoding=UTF-8 -J-client -J-Xss2m ........."

13 nov 2016

Números muy grandes, con todos sus dígitos, en Java

¿Cómo calcular la última cifra de un número muy grande, por ejemplo 22016? ¿Y si quisiéramos también la penúltima cifra? Es un problema típico, por ejemplo, para fomentar el razonamiento en las olimpiadas matemáticas.

Si analizamos las potencias de 2, observamos que se repite la serie de terminaciones 2  4  8  6, por lo que necesariamente tiene que ser uno de estos dígitos.
21 -> 2
22 -> 4
23 -> 8
24 -> 16
25 -> 32
26 -> 64
27 -> 128
28 -> 256
29 -> 512
210 -> 1024
211 -> 2048
212 -> 4096
213 -> 8192
214 -> 16384
215 -> 32768
216 -> 65536
217 -> 131072
218 -> 262144
219 -> 524288
220 -> 1048576

Si dividimos 2016 entre 4 terminaciones, nos da 54 grupos y de resto 0, por lo que la última cifra tiene que ser la última del grupo, es decir 6.

Si queremos la penúltima cifra tenemos que analizar los dígitos que acompañan al 6, que también son una serie, en este caso de cinco elementos, 1  5 9 3 y 7. Si ahora dividimos nuestros 54 grupos entre cinco, obtenemos 10, y de resto 4, por lo que el penúltimo número será el cuarto de la serie, es decir el 3.

Pero, ¿por qué no obtener todas las cifras del número? Pues aparentemente algo tan sencillo no es posible en muchas calculadoras a no ser que se utilice la notación científica. Pero el problema es que con esta notación no vemos todas las cifras. Si recurrimos a los lenguajes de programación el problema viene dado por los tipos para representar los enteros, los int, double, o  long, no nos permiten almacenar números tan grandes. La solución viene por utilizar clases especializadas como puede ser la clase BigInteger de Java.

El siguiente código nos muestra el resultado de 22016, aún más, muestra todas las potencias de 2, hasta 2016.

import java.math.BigInteger;

public class Potencias {

    public static void main(String[] args) {

       BigInteger resul = new BigInteger("1");

       BigInteger dos = new BigInteger("2");

       for (int i=1; i<=2016; i++){
           resul= resul.multiply(dos);
           System.out.println("2^"+ i + " -> " + resul+ "\n");
       }
    }
}

Y de esta forma, podemos ver que 22016 es un número de sólo 607 cifras:

7524389324549354450012295667238056650488661292408472865850279440061341770661038088891003609523855490537527473858068236032063038821912119420032983735773778315780422968627185582125139830259059580693966159220800634538007951025529707819651368618588002973837229854435730968342995245834129352264002058451047722604571453619205472623157541916371455764131661512732115122042085430429090324954236930736866452001076451671762299658372499364800367306988138217572983729940207496105489713305332746758395131148149101871456611571055068153665866066783899124296271513772531723497342815490725823828326183977758546404902789185536

10 oct 2016

Manejo de fechas en Java

java.util.Date


Durante mucho tiempo la clase utilizada para manejar fechas fue java.util.Date, pero hoy día muchos de sus métodos se consideran obsoletos.

java.util.Calendar


Posteriormente se utilizó la clase java.util.Calendar y java.util.GregorianCalendar. Su principal diferencia con Date, es que Calendar es mutable, permite cambiar el valor de un objeto sin tener que generar un nuevo objeto.
Calendar es una clase abstracta por lo que sus objetos deben instanciarse a través de alguna de sus clases heredadas, por ejemplo con el método getInstance() que nos devolvería la fecha actual del sistema o bien con GregorianCalendar, que sí es instanciable. Ejemplos:

Calendar hoy= Calendar.getInstance();
Calendar hoy= new GregorianCalendar();

Calendar dispone de métodos “get” y “set” que permiten recuperar o almacenar valores de las fechas, los correspondientes al argumento pasado, pero también tiene métodos “add” y “roll” que permiten a partir de unos valores incrementar o decrementar las fechas, recalculando las fechas correctas si es necesario.

Ejemplo:


Si quisiéramos darle valores, por ejemplo de una fecha de nacimiento, sería:



La clase Calendar permite trabajar de dos modos, en un modo indulgente o permisivo (Lenient), que es el que está activo por defecto, y que nos permitiría almacenar fechas sin realizar validaciones, por ejemplo darle al atributo día el valor 34, o no tener en cuenta sin un año es bisiesto, y un modo más restrictivo, que produciría una excepción de tipo IllegalArgumentException, si los parámetros de la fecha no son correctos.
Para activar el modo restrictivo, o no indulgente, pondríamos:
nacim.setLenient(false);

El ejemplo anterior con una fecha no válida, daría el siguiente resultado:



Paquete java.time


A partir de la versión 8 de Java se recomienda el uso del paquete java.time. Este paquete contiene clases para tratar fechas, tiempos, instantes y duraciones. Por ejemplo LocalTime, permite almacenar horas sin fecha, como: 14:37:26;  LocalDate, sirve para almacenar fechas sin hora, como 2015-05-22; LocalDateTime, permite almacenar fechas con hora.

LocalDate

Los objetos se crean con el método estático of() de la clase LocalDate.
LocalDate fecha = LocalDate.of(2016, 3, 27);

Si lo que queremos es obtener la fecha actual del sistema, utilizaremos el método now():
LocalDate ahora = LocalDate.now();

Dispone de métodos get para obtener los componentes día, mes y año de una fecha:
int dia, mes, anno;
dia= fecha.getDayOfMonth();
mes= fecha.getMonthValue(); //devuelve valores 1..12
anno= fecha.getYear();

Otros métodos get:
DayOfWeek diaSemana= fecha.getDayOfWeek();
Month nombreMes= fecha.getMonth();

Tiene métodos para incrementar, plus(), o para hacer retroceder una fecha, minus(). Estos métodos admiten dos argumentos el primero es el número de unidades y el segundo que expresa el tipo de unidades, mediante el tipo enumerado ChronoUnit, que tiene valores como DAYS, WEEKS, MONTHS o YEARS. Ejemplos:
LocalDate fecha2= fecha1.plus(5, ChronoUnit.MONTHS); //Aumenta fecha1, 5 meses
LocalDate fecha2= fecha1.minus(3, ChronoUnit.DAYS); //Disminuye fecha1, 3 días

Otros métodos interesantes:
·         isLeapYear()      verifica si un año es bisiesto
·         until()                   permite calcular el tiempo entre dos fechas
·         parse()                 permite analizar una cadena para generar una fecha

Para establecer el formato de las fechas, se utiliza la clase DateTimeFormatter.

3 dic 2015

Formatos en ficheros de texto

Se trata de dotar de una organización interna a los ficheros de texto.

El caso extremo sería, sin ningún formato, en este caso sería una secuencia de caracteres de principio a fin. Sería el caso, por ejemplo, de muchos libros disponibles en la red. Su utilidad en muchos casos es el análisis de determinados aspectos lingüísticos.

Los principales formatos serían:
Con longitud de campos fija.
Con separador entre campos.
Con un campo por línea.



Con longitud de campos fija o encolumnados.

Cada campo se almacena con una longitud fija para todos los elementos.
Es necesario conocer estos valores de longitudes para poder leer y escribir.
Supongamos que queremos guardar datos de empleados (dni (9 caracteres), edad (2 dígitos), nombre (20 caracteres) y salario mensual (5 dígitos y 2 decimales). Podríamos escribir con un formato  %9s%2d%20s%7.2f\n

Ejemplo:
14567432F32    Ana Martín Pérez 1654.43
 4322224M28Manuel Rodríguez Mar 998.32
  657897H45  Jesús López Castro10234.23   



Con separador entre campos.

Cada campo está separado del siguiente por un separador, que puede ser cualquier carácter, pero que debe ser elegido de forma que no forme parte del contenido de los campos.  Caracteres habituales son el tabulador, la coma (,), el punto y coma (;), los dos puntos (:), los dobles dos puntos (::),  la almohadilla (#),  asterisco (*), etc.
El separador (,) es muy habitual dando nombre a un tipo de ficheros llamado .csv (del inglés comma-separated values).
La mayoría de las hojas de cálculo, los procesadores de textos y los sistemas gestores de bases de datos permiten importar y exportar a este formato.

Ejemplo:

14567432F#32#Ana Martín Pérez#1654.43
4322224M#28#Manuel Rodríguez Mar#998.32
657897H#45#Jesús López Castro#10234.23


Por ejemplo, en Word, nos preguntaría por el tipo de codificación del fichero:



En Excel, nos permitiría separarlo en columnas, indicándole primero la codificación y posteriormente la separación:




En otras hojas de cálculo de forma muy similar, como en Calc de Libre Office:



Con un campo por línea.

Aparece cada campo en una línea.

Ejemplo:
14567432F
32
Ana Martín Pérez
1654.43
4322224M
28
Manuel Rodríguez Mar
998.32
657897H
45
Jesús López Castro
10234.23


18 nov 2015

Acceso aleatorio a ficheros en Java

Java dispone de la clase RandomAccessFile que permite acceder al contenido de un fichero binario de forma aleatoria.

Para crear objetos de esta clase, al constructor se le puede pasar, o la ruta mediante un String o mediante un File. En cualquiera de los dos casos hay que pasar un segundo parámetro con el modo de acceso. El modo de acceso puede ser “r”, “rw”

Ejemplos:
Fich= new RandomAccessFile(String nombre, String modoAcceso)
Fich = new RandomAccessFile(File fic, String modoAcceso)


Los métodos más importantes son:
long getFilePointer()
Devuelve la posición del puntero en el fichero.
void seek(long pos)
Pone el puntero en la posición dada por pos, tomada desde el inicio.
long length()
Devuelve el tamaño del fichero en bytes.
Int skipBytes (int desplaz)
Mueve el puntero desde su posición el número de bytes indicado por desplaz.

Ejemplo:
Vamos a guardar los datos de una serie de empleados. Interesa que los datos de cada persona tengan la misma longitud para poder saltar e ir a una persona, para ello limitamos (fijamos) el tamaño de los String antes de escribirlos:

buffer = new StringBuffer(apellidos[i]);
buffer.setLength(10);

El resto de campos tienen una longitud fija, porque son enteros y double.
En total: int (4 bytes), string (20 bytes), int (4 bytes) y double (8 bytes) = 36 bytes.
Tamaño del fichero= 8 registros x 36 bytes = 288 bytes

public class EscribirFichAleatorio {
public static void main(String[] args) throws IOException {
        // TODO code application logic here
        File fich = new File("Empl_aleat.dat");
        RandomAccessFile file = new RandomAccessFile(fich, "rw");
        String[] apellidos = {"MARTÍN", "SORIA", "FERNÁNDEZ", "LUNA", "GARCÍA", "PÉREZ", "RODRÍGUEZ", "MARTÍNEZ"};
        int[] dept = {10, 20, 30, 20, 10, 40, 30, 40};
        Double[] salario = {850.65, 12035.36, 2156.36, 1500.32, 989.23, 1566.32, 1866.88, 2356.78};
        StringBuffer buffer = null;
        for (int i = 0; i < apellidos.length; i++) {
            file.writeInt(i + 1); //identificador de empleado
            buffer = new StringBuffer(apellidos[i]);
            buffer.setLength(10);
            file.writeChars(buffer.toString());
            file.writeInt(dept[i]);
            file.writeDouble(salario[i]);
        }
        file.close();
    }
}