Ordenar un Vector con Hashtables en Java

Publicado: marzo 18, 2011 en Java
Etiquetas:, , ,

Soy Ingeniero Electrónico pero actualmente trabajo en Informática. En este blog suelo comentar cualquier tontería que se me venga a la cabeza o vea por ahí, pero nunca he hecho una entrada de tipo técnico. Se me presentó un caso poco común en el trabajo y dado que me parece bastante útil la solución, lo comparto aquí.

En el proyecto donde estoy ahora, manejamos aplicaciones web que recuperan divera información de las bases de datos de la empresa. Al recuperarlas, por la arquitectura y lógica con la que se diseñaron las aplicaciones, se almacena cada registro en una Hashtable usando como clave el nombre del campo, y luego las junta todas en un Vector para que sea más sencillo trabajar con dichos datos. En este caso teníamos un Vector con datos como estos:

[{ COD_USUARIO="A100001", PUNT_1=9.45, PERCENT_1=0, PUNT_2=7.45, PERCENT_2=0},
 { COD_USUARIO="A100002", PUNT_1=7.89, PERCENT_1=0, PUNT_2=3,75, PERCENT_2=0},
 { COD_USUARIO="A100003", PUNT_1=3.67, PERCENT_1=0, PUNT_2=7.36, PERCENT_2=0},
 { COD_USUARIO="A100004", PUNT_1=5.54, PERCENT_1=0, PUNT_2=8.35, PERCENT_2=0},
 { COD_USUARIO="A100005", PUNT_1=6.28, PERCENT_1=0, PUNT_2=9.58, PERCENT_2=0}]

Con esta información, debíamos realizar algunos cálculos que requerían que los datos estuvieran ordenados ascendentemente en un caso, usando como referencia la columna PUNT_1, y en otro la columna PUNT_2.

Lo más sencillo es usar un ORDER BY a la hora de recuperar los datos y almacenar los resultados en dos Vectores distintos, pero las queries tardaban mucho en ejecutarse y lo mejor era acceder a la base de datos lo menos posible. Había que recuperar los datos una sóla vez y luego ordenar el Vector de acuerdo al criterio que se pidiera.

Googleando un poco, encontramos varios ejemplos que explicaban como ordenar un Vector haciendo uso del método sort de la clase Collections, pero siempre hablaban de vectores que contenían una sola columna de datos. Por ejemplo, algo así:

 [{"Uno"},
 {"Dos"},
 {"Tres"} ,
 {"Cuatro"} ]

Esto no nos servía de mucho ya que nuestro Vector cuenta con varias columnas. Entonces, encontramos un ejemplo en Developer Papercuts, que nos iluminó bastante. Haciendo uso de la interfaz Comparable, podíamos especificar el criterio de comparación entre dos elementos cualquiera, inclusive – como ya imaginarán – Hashtables.

Recapitulando, necesitamos una clase que extienda de la Interfaz Comparable para reescribir su método compare, el cuál recibirá como parámetro dos Hashtables, recuperará el valor del campo indicado. Como son números decimales, los parseará, los comparará y devolverá 1 si el primer dato es mayor que el segundo, 0 si son iguales, y -1 si el segundo es mayor que el primero. Además, dicha clase debe recibir en el constructor el nombre de la columna que se usará para ordenar los datos. Después de escribir un poco, tenemos esto:

/**
 * Clase que ordena hashtables de manera ascendente de acuerdo al valor del
 * campo indicado en el constructor
 *
 */
private class Comparador implements Comparator<Hashtable> {
	String sNombreCampo = "";

	public Comparador (String sCampo) {
		sNombreCampo = sCampo;
	}

    public int compare(Hashtable ht1, Hashtable ht2) {
    	// Se recuperan los datos
    	String sDato1 = (String) ht1.get(sNombreCampo);
    	String sDato2 = (String) ht2.get(sNombreCampo);

    	// Como son puntuaciones se convierten a números
    	double d1 = Double.parseDouble(sDato1);
    	double d2 = Double.parseDouble(sDato2);

    	// Se comparan las puntuaciones
        return (d1>d2 ? 1 : (d1==d2 ? 0 : -1));
    }
}

Y listo. Ahora, haciendo uso del método sort de  la clase Collections ya puedo ordenar el Vector de datos así:

Collections.sort(vDatos, new Comparador(sNombreColumna))

Donde sNombreColumna sería, en un caso, PUNT_1 y en otro, PUNT_2.Y con eso se solucionó el problema. Como mencioné líneas arriba, la interfaz Comparador puede servir para crear comparadores para cualquier objeto, con lo puede servir para comparar cadenas de caracteres, números, Arrays de datos y muchas más cosas.

ACTUALIZACIÓN: Si se están preguntando como insertar código en WordPress, pueden verlo aquí.

Anuncios
comentarios
  1. ignorante dice:

    Comentarios:
    1) Se pueden obtener los valores numéricos en forma de double de la base haciendo resultSet.getDouble(“columna”) en lugar de resultSet.getString(“columna”). Eso te ahorra el parseo del double que es costoso y que lo estás realizando al menos 2*n*log(n) veces al ordenar (mucho más de 1 vez cada uno).
    2) En lugar de HashTable, usa HashMap que no es sincronizada y además declara la variable como Map (la interfaz) en lugar de con HashMap (la clase).
    3) Si usas java desde la versión 1.5 en adelante, puedes usar generics para evitar escribir los castings.

    • allo86 dice:

      Hola. Gracias por los comentarios. No tengo tanto conocimiento del tema todavía, pero quería aclarar un par de cosas sobre esto:

      1) Lo que indicas es correcto para los ResultSets, pero en este caso, dada la arquitectura de la aplicación para la que querían hacer este proceso, usan Hashtables y tenía que leerlo así. Es decir, ya recibimos ese Hashtable creado, y al recuperar los valores de este, creo que no tengo otra forma de hacerlo que primero como String y luego parsear a double.

      2) Te doy la razón en que la HashMap es mucho mejor, pero nuevamente recalco que el escenario (aplicación ya hecha por otras personas) tenía limitaciones y cada consulta a la BBDD la devuelve como hashtable (hereda de una clase padre que tiene como su propia API para consultas a BBDD y sólo podemos usar eso).

      3) Lo de los generics lo conozco poco. Lo pusimos en la declaración del Comparador (antes no salía, ahora sí) pero fue más por casualidad que por conocimiento real. Lo que me quieres decir es que, en la definición del método compare, yo hago algo así como Hashtable ht1, ¿me ahorraría los castings luego? ¿Lo entendí bien?

  2. ignorante dice:

    Tienes el problema de la cantidad de invocaciones a parseDouble. Estás parseando muchas veces cada uno de los strings.
    Yo haría así el comparator y mediría si es más rápido.

    public class DoubleStringComparator implements Comparator<Hashtable> {

    private String sNombreCampo;
    private Map values;

    public DoubleStringComparator(String sCampo) {
    sNombreCampo = sCampo;
    values = new HashMap();
    }

    @Override
    public int compare(Hashtable ht1, Hashtable ht2) {
    // Se recuperan los datos
    String sDato1 = ht1.get(sNombreCampo);
    String sDato2 = ht2.get(sNombreCampo);

    Double d1 = values.get(sDato1);
    if(d1 == null){
    d1 = Double.valueOf(sDato1);
    values.put(sDato1, d1);
    }
    Double d2 = values.get(sDato2);
    if(d1 == null){
    d2 = Double.valueOf(sDato2);
    values.put(sDato2, d2);
    }
    return d1.compareTo(d2);
    }
    }

    • allo86 dice:

      Es una solución interesante la tuya, y la única manera de salir de dudas sería medirla. Cuando pueda lo haré, para ver si me compensa reemplazar un parseDouble por un if y un acceso a una HashMap (eso en el mejor de los casos, que en otros tambien tengo que hacer el parse igual). Gracias por el comentario!

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s