Contenidos mínimos y resumidos del Curso


1. Para definir un tipo Base (como Punto, Circulo, Libro, Vuelo)

·       Se crea una interfaz con el nombre del tipo.

·       En la interfaz se ponen los prototipos de las funcionalidades que va a tener el tipo base.

·       Debe haber métodos get (que devuelven un tipo con alguna propiedad básica del tipo que estamos definiendo y no reciben argumentos) y métodos set (que reciben un argumento y devuelven void).

·       Puede haber métodos get para devolver propiedades derivadas que se calculan a partir de las propiedades básicas. Por ejemplo el área de un Circulo.

·       Puede haber otros métodos para manejar el tipo, tales como distancia a otro punto, rebajar el precio de un libro un porcentaje, si un punto está dentro de un círculo, etc.

·       Se crea una clase con el nombre del tipo más el sufijo Impl.

·       No olvide añadir la interfaz cuando crea la clase para que en el código aparezcan las cabeceras de los métodos que hemos definido en la interfaz.

·       Lo primero es declarar con la cláusula private a los atributos que soportan el tipo (coordenadas reales en el tipo Punto, titulo y autor de tipo String, número de páginas entero en Libro, centro de tipo Punto en el Circulo) con su tipo e identificador

·       Se escribe el código de los métodos get, básicamente consiste en un return del atributo correspondiente.

·       Se escribe el código de los métodos set, básicamente cambiar el valor del atributo por el argumento que se ha pasado al método.

·       Se escribe el código del método equals. Su cabecera siempre es la misma y su código sólo se diferencia en el tipo del instanceof, del casting y de los atributos que debo comparar. Los atributos se comparan invocando con equals a los métodos get con el objeto implicito this y con el objeto pasado como argumento. Las propiedades implicadas en la comparación deben ser señaladas en el diseño del tipo.  Serán las mismas para el toString y para el compareTo en el caso de que lo haya.

·       Se escribe el  código de toString. Su cabecera siempre es igual y debe devolver una representación del objeto como String. Para ello estarán implicados los mismos atributos que estaban presente en el equals.

·       Si el tipo va a tener orden natural, esto es, queremos ordenar (o hallar el máximo o el mínimo) teniendo en cuenta las propiedades implicadas en el equals, entonces definimos el tipo como Comparable. Para ello, en la interfaz ponemos “extends Comparable<TIPO>” donde en TIPO se pone el nombre de la interfaz (Punto, Libro, etc)

·        A continuación en la clase se implementa el método compareTo en cuya cabecera se define que devuelve un int y recibe un argumento del tipo que estamos comparando. El código del método compareTo es una sucesión de llamadas al método compareTo aplicado a las propiedades del tipo implicadas en el equals. Estas comparaciones se hacen entre las invocaciones de los métodos get con el objeto this y los métodos get del objeto que se pasó como argumento. El orden en el que se realizan estas comparaciones debe ser decidido en el enunciado. Después de cada comparación si el valor devuelto es cero, se hará otra comparación implicando a la propiedad (atributo) siguiente.

·       Si el tipo base va a estar en una colección que use los tipos Set o Map deberá definirse el método hashCode, que devuelve un valor de tipo int, calculado mediante alguna operación aritmética en base a los métodos hashCode de las propiedades que definen el equals.

·       Finalmente proporcionamos los constructores que son métodos que se denominan igual que la clase y que no devuelven ningún tipo (ni siquiera void). Una clase puede tener varios constructores, lo habitual es que al menos uno se defina para recibir tantos argumentos como atributos tenga el tipo. Si existe alguna restricción para alguna propiedad (por ejemplo, el radio del círculo debe ser positivo) se debe preguntar y lanzar una excepción creada o IllegalArgumentException. En algunos casos se programan constructores sin argumentos que crean objetos con valores por defecto para sus atributos. También se puede proporcionar un constructor a partir de un String que contenga toda la información necesaria para construir un objeto, por ejemplo, separando los valores de los atributos por comas. Es el constructor que se usará para crear una colección de objetos a partir de una lectura de fichero de texto.

2.  Para definir un tipo Colección (como NubePuntos, Librería, Aeropuerto)

·       Los tipos colección sirven para encapsular un conjunto de funcionalidades con la información que suministra una colección de elementos. La implementación de la información del tipo se hace mediante un atributo normalmente de tipo List o Set que almacena la información. La implementación de los métodos se basa en los métodos de los tipos List y Set (add, addAll, contains, size, etc) y los métodos de la clase de utilidad Collections (sort, max, min, etc).

·       En primer lugar definiremos la interfaz para el tipo colección escribiendo en ella los prototipos de los métodos que aportan funcionalidad al tipo colección que estamos diseñando. Hay que recordar que estos métodos serán invocados por un objeto del tipo colección y que por tanto, operan sobre el atributo de tipo List (o Set) correspondiente. Esto es, el argumento de tipo colección ya está implícito y no estará presente en la signatura de los métodos. Así si definimos en la clase Test un objeto np de tipo NubePuntos, las invocaciones a los métodos serán:


Integer n=np.getContadorPrimerCuadrante();
Double m=np.getSumaAbscisaMayor(Double nx);


·         Las funcionalidades más habituales para los tipos colección son:

o   Métodos añadir, borrar, contiene, unión, intersección, etc. son métodos que encapsulan los métodos add, remove, contains, addAll, retainAll, etc del tipo List. Por tanto, tendrán su misma signatura.

o   Métodos contadores devuelven un Integer o un Long. Responden a preguntas del tipo ¿Cuántos puntos cumplen X? ¿Cuántos libros tienen Y? Recibirán los argumentos necesarios para implementar las condiciones X o Y en el caso de que las haya.

o   Métodos sumadores o en general acumuladores (incluye media, varianza y similares) que normalmente devuelven un tipo numérico (entero o real).  Responden a preguntas del tipo ¿Cuánto suman las abscisas de los puntos que cumplen X? ¿Cuál es el precio medio de los libros que tienen Y? Recibirán los argumentos necesarios para implementar las condiciones X o Y en el caso de que las haya.

o   Métodos existe que devuelven un tipo Boolean.  Responden a preguntas del tipo ¿Existe algún punto que cumpla X? ¿Existe algún libro que tenga Y? Al igual que los casos anteriores estos métodos recibirán argumentos para poder implementar las condiciones X o Y.

o   Métodos paraTodo que devuelven un tipo Boolean.  Responden a preguntas del tipo ¿Todos los puntos cumplen X? ¿Todos los libros tienen Y?

o   Métodos de ordenación, que modifican la posición de los elementos de la colección de acuerdo a un determinado orden. En general en la POO los métodos que modifican el estado del objeto que invoca son de tipo void, esto es, no devuelven otra colección distinta y ordenada. Para ordenar en una colección diferente lo más apropiado es hacer previamente una copia de la colección.

o   Métodos máximo/mínimo que devuelven un objeto del tipo base.  Responden a preguntas del tipo ¿Cuál es el punto de mayor abscisa? ¿Cuál es el punto más cercano al origen? ¿Cuál es el próximo vuelo? ¿Cuál es el libro más barato? Si el máximo/mínimo es de una colección parcial (previo filtro) deben pasarse como argumento del método los parámetros necesarios. Por ejemplo, dado un destino cuál es el vuelo más barato, debe recibir un String con el destino.

o   Métodos filtro que devuelven un objeto de tipo colección con sólo aquellos objetos de la colección invocante que cumplan una determinada condición. Los métodos filtro normalmente no deben devolver un List o un Set de objetos, sino un objeto de tipo colección como el que estamos diseñando. De esta forma se pueden hacer invocaciones a otros métodos de la clase. Por ejemplo si tenemos un método que devuelva el centro de gravedad de una NubePuntos y un método que filtra aquellos puntos que son del primer cuadrante, es fácil hallar el centro de gravedad de los puntos del primer cuadrante en la clase Test mediante una llamada:

Punto cg1=np.filtroPrimerCuadrante().
                      tCentroGravedad();

·       Una vez declarada la interfaz, implementamos la clase para esa interfaz. Para ello construimos una clase terminada en Impl y no olvidamos añadir la interfaz para que se nos copien en el código de la clase las cabeceras de los métodos.

·       En la clase lo primero que escribimos es el atributo privado de tipo List (o Set)
private List atributo_lista;

·         Los métodos añadir, borrar, etc. tendrán una sola invocación al método de List/Set correspondiente: add, remove, etc.

·         Implementamos los métodos definidos en la interfaz con los esquemas siguientes:

·         Métodos contadores:

    Integer cont=0
    for(TipoBase e: Lista){
        if (Condición sobre e){
            cont++
        }
    }
    return cont


·         El esquema sumador/acumulador/media es:

    Integer/Double suma=0
    for(TipoBase e: Lista){
       if (Condición sobre e){
              suma = suma + expresión sobre e
       }
    }
    return operación sobre suma
 

·         El esquema existe es:

    Boolean existe=false
    for(TipoBase e: Lista){
       if (Condición sobre e){
             existe=true
             break
       }
    }
    return existe

 
·         Métodos paraTodo:

    Boolean paraTodo=true
    for(TipoBase e: Lista){
       if (No Condición sobre e){
            paraTodo=false
            break
       }
    }
    return paraTodo
 

·         Para implementar los métodos de ordenación, hay que tener en cuenta que el tipo List en Java es ordenable pero el tipo Set no. Si se quiere recurrir a una colección de elementos no repetidos pero ordenable, la implementación de la colección debe hacerse usando como atributo un SortedSet. Aunque existen numerosos algoritmos de ordenación, es más sencillo usar el método sort de la clase Collections. Si la colección debe ser ordenada por el orden natural del tipo base (que debe implementar Comparable y tener método compareTo) basta con invocar a Collections.sort(atributo_lista). Si el orden a implementar es un orden distinto al natural debe implementarse un comparador externo mediante una clase que implemente un Comparator según el siguiente punto.

Collections.sort(atributo_lista, new ComparadorPorPropiedad());

·       Un comparador externo o Comparator se usa para ordenar o hallar el máximo/mínimo de una colección de elementos por un orden diferente al natural. Para ello debe definirse una clase que implemente a Comparator (no olvide añadir la interfaz Comparator al crear la clase). El código de una clase comparador es similar a:

public class ComparadorPorPropiedad implements Comparator {
  public int compare(TipoBase arg1, TipoBase arg2){
     Tipo t1=arg1.getPropiedadAComparar();
     Tipo t2=arg2.getPropiedadAComparar();
       
     return t1.compareTo(t2);
  }
}

El orden de este comparador es de menor a mayor, si se necesitase un orden de mayor a menor sería necesario cambiar la sentencia return por t2.compareTo(t1). Si el comparador se usa para definir un SortedSet además es necesario romper el empate por el compareTo del tipo base:

public class ComparadorPorPropiedad implements Comparator {
  
  public int compare(TipoBase arg1, TipoBase arg2){
     Tipo t1=arg1.getPropiedadAComparar();
     Tipo t2=arg2.getPropiedadAComparar();
         
     int comp=t1.compareTo(t2);
     if (comp==0){
          comp=arg1.compareTo(arg2);
     }
     return comp ;
   }
}

·       El algoritmo de máximo/mínimo global (para toda la colección) también es un esquema clásico, sin embargo, se puede resolver en Java invocando la método max/min de la clase Collections. Si el máximo/mínimo es por el orden natural basta con invocar al método max/min de Collections con el argumento del tipo List/Set.

return Collections.max(atributo_lista);

                Si es por un orden externo, hay que hacerlo con un Comparator

return Collections.max(atributo_lista, new ComparadorPorAtributo);

·       El algoritmo de máximo/mínimo con filtro es un esquema diferente. Si nos piden el máximo/mínimo de un subcolección. Hay dos posibilidades, la primera construir un método filtro para nuestra colección y después invocar al método máximo/mínimo global desde la colección filtrada. La segunda es hacer el filtro mediante un List/Set dentro del método e invocar al método max/min de Collections con esa List como argumento:

 TipoBase método_maximo/minimo(argumentos para la condición){
      List<TipoBase> v = new ArrayList<>();
         
      for(TipoBase e:lista_atributo){
         if(condicion)){
              v.add(e);
         }
      }
     return Collections.max/min(v, new ComparadoPorAtributo());
  }

·       Los métodos de filtro se pueden hacer con dos esquemas. Bien creando un objeto de tipo colección vacío y mediante un bucle añadirle los elementos que cumplan la condición mediante un método añadir del tipo colección:

Tipo_Colección método_filtro(argumentos para la condición){
      Tipo_Colección v = new Tipo_ColecciónImpl()
      for(TipoBase e:lista_atributo){
           if(condición){
                v.añade(e) // no se puede poner add
           }
      }
      return v
}

  
La otra opción es trabajar a nivel de List/Set y después invocar a un constructor del tipo colección que reciba como argumento un objeto List:


Tipo_Colección método_filtro(argumentos para la condición){
     List/Set<TipoBase> v = new ArrayList<>/HashSet<>();
       
     for(TipoBase e:lista_atributo){
         if(condicion)){
              v.add(e); // aquí sí
         }
     }
     return new Tipo_ColeccionImpl(v);
}

 

·       Los constructores para el tipo colección pueden ser diversos. Dos los hemos usado en las dos opciones anteriores de métodos filtro.  Un constructor sin argumentos:

public Tipo_ColeccionImpl(){
      lista_atributo =new ArrayList<>/HashSet<>();
}

 
        O bien un constructor que reciba un List/Set de objetos de tipo base:
 

public Tipo_ColeccionImpl(List ls){
   lista_atributo= new ArrayList<>/HashSet<>(ls);
}

4.3 Como leer una colección de objetos de un fichero

Supongamos un fichero de texto que cada línea contiene la información necesaria para crear un objeto del tipo base. Por ejemplo para el tipo Vuelo:

       Madrid,12.37,155,100,IB1123,22,11,2007
       Barcelona,19.56,200,150,VLG256,22,11,2007
       Valencia,2.1,150,150,RYA803,22,11,2007
      


Se necesita leer la información del fichero y construir una colección de objetos con ella. Los pasos son tres:

·       Proporcionar al tipo base un constructor a partir de un String (como de hecho tienen otros tipos de Java como Integer, Double o LocalDate con el método parse). Para ello supondremos que los valores de los atributos están separados por un(os) determinados caracteres y usaremos el método split de String que recibe una expresión regular con los posibles separadores. El código sería:

public TipoBaseImpl(String cadena){
   List<String>lisat = Arrays.asList(cadena.split("[sep]+"));
   if (lisat.size()!=n){
        throw new IllegalArgumentException("formato de
                   cadena no valido "+lisat.size());
   }
   atributo1=new [Double/Integer/otro] lisat.get(0));
   atributo2=new [Double/Integer/otro] (lisat.get(1));
  
   atributon=new [Double/Integer/otro] (lisat.get(n-1));
}

 
O bien una version con arrays:

public TipoBaseImpl(String cadena){         
    String [] lisat = cadena.split("[sep]+");
    if (lisat.length!=n){
        throw new IllegalArgumentException("formato de
                      cadena no valido "+lisat.size());
    }
    atributo1=new Double/Integer/otros (lisat[0]);
    atributo2=new Double/Integer/otros (lisat[1]);
    
    atributon=new Double/Integer/otros (lisat[n-1]);
}

 

Donde la expresión que aparece como argumento del método split es una expresión regular con los posibles separadores de los valores de los atributos. Si sólo se va a usar un separador lo más fácil es poner “,” o “;”. Si hubiera dos o tres se pueden poner expresiones como “[,;.]+” .
 

Respecto a la conversión de un String a cada uno de los tipos de los atributos depende lógicamente del tipo. El tipo String no lo necesita. Los tipos wrappers como Integer, Double o Boolean tienen su propio constructor a partir de String. Los tipos enumerados se deben construir mediante el método valueOf y atributos de tipo LocalDate con el método of (en formato año, mes, día). Por ejemplo, para los siguientes atributos del tipo Experimento:

public class ExperimentoImpl implements Experimento {
       private String codigo;
       private Bacteria nomBacteria;
       private Integer numColonias;
       private Double temperatura;
       private LocalDate fechafinal;
       private List resultados;


                El constructor quedaría:

 
public ExperimentoImpl(String s){
    List<String>trozos = Arrays.asList(s.split(","));
    codigo=trozos.get(0);
    nomBacteria=Bacteria.valueOf(trozos.get(1));
    numColonias=new Integer(trozos.get(2));
    temperatura= new Double(trozos.get(3));
    fechafinal=LocalDate.of(new Integer(trozos.get(6)),
                       new Integer(trozos.get(5)),
                       new Integer(trozos.get(4)));     
    Integer num = new Integer(trozos.get(7));
    resultados = new ArrayList<>();
    for (int i=0;i<num;i++){
          Double x = new Double(trozos.get(8+i));
          resultados.add(x);
    }
}


·         El segundo paso es encapsular la lectura del fichero por líneas. Esta tarea se podría hacer en el mismo constructor del tipo colección, pero teniendo en cuenta que es un proceso estándar que no depende del tipo, se puede implementar mediante un método de utilidad:


public static List<String>leeFicheroPorLinea(String fileName) {
       List<String> listaleida= new LinkedList();
       try {
          Scanner sc = new Scanner(new File(fileName));
          while (sc.hasNextLine()) {
                 listaleida.add(sc.nextLine());
          }
          sc.close();
       } catch (FileNotFoundException e) {
          System.out.println("Fichero no encontrado");
       }
       return listaleida;
}


Este método devuelve un List de String donde cada cadena contiene una línea del fichero, que es justo la información que el constructor anterior “convierte” en un objeto del tipo base.

·       El tercer paso es escribir el método constructor del tipo colección que recibe como argumento el nombre de un fichero, deberá leerlo por líneas mediante el método anterior, invocar al constructor del tipo base del primer paso y finalmente añadirlo en el atributo de tipo Collection que conforma el tipo colección. Por ejemplo, para un atributo lista_atributo de tipo List sería como sigue (para Set o SortedSet sustituir por la clase correspondiente):

 
public TipoColeccionImpl(String nomFile){
       List <String>ls = Utiles.leeFicheroPorLinea(nomFile);
       lista_atributo = new ArrayList();
       for (String linea:ls){
           TipoBase p = new TipoBaseImpl(linea);
           lista_atributo.add(p);
       }
}


3. Como crear un Map desde una Collection

Un Map no es más que la relación o aplicación entre dos tipos de datos, los del conjunto original se llaman claves y los del conjunto imagen valores. Se pueden usar como un organizador de información (lista de vuelos por fecha, lista de puntos por cuadrante) o para cuantificadores múltiples (contador de vuelos por destino, recaudación por fecha, vuelo más barato por destino, etc).

La algoritmia para inicializar un objeto de tipo Mapa partir de un iterable de tipo Collection siempre sigue la misma estructura, que se puede ver en el esquema siguiente:

a.       Creación del objeto (HashMap)
b.      Para cada clave
                                                             c.      Si la clave ya está en el Map (containsKey)
d.      Obtener el valor asociado a esa clave (get)
e.      Actualizar el valor u obtener nuevovalor a partir de valor
f.        Si es necesario, añadir al Map el par clave, nuevovalor (put)

                                                             g.      Si la clave no está en el Map
h.      Inicializar valor
i.         Añadir al Map el par clave, valor (put)

Este esquema general se puede implementar en dos variantes, según el tipo del conjunto de valores sea un valor inmutable o una colección.  Veamos estos dos casos por separado:

·       Si queremos un cuantificador múltiple, por ejemplo un contador por cada valor de una propiedad del tipo base. El esquema sería

 

public Map<TipoPropiedad,Integer> getCuantificadorPorPropiedad(){
       Map <TipoPropiedad, Integer> mp = new HashMap<>();
       for(TipoBase e: lista_atributo){
             TipoPropiedad tp = e.getPropiedad();
             if (mp.containsKey(tp)){
                    Cuantificador cont = mp.get(tp);
                    Actualizar cont
                    mp.put(tp,cont);
             }
             else{
                    Inicializar cont
                    mp.put(tp,cont);          
             }
       }
       return mp;
}     

 

Por ejemplo, obtener la relación del número de vuelos por fecha:

 Map<LocalDate,Integer> getContadorVuelosPorFecha(){
     Map<LocalDate,Integer>mp=new HashMap<>();
     for(Vuelo e: vuelos){
         LocalDate fecha = e.getFecha();
         if (mp.containsKey(tp)){
               Integer cont = mp.get(fecha);
               cont++;
               mp.put(fecha,cont);
         }
         else{
               Integer cont=1;
               mp.put(fecha,cont);       
         }
    }
    return mp;
}


                O la recaudación por destino:


public Map<LocalDate,Double> getRecaudacionPorFecha(){
     Map<LocalDate,Double> mp = new HashMap<>();
     for(Vuelo v: vuelos){
         LocalDate fecha = v.getFecha();
         if (mp.containsKey(fecha)){
             Double rec = mp.get(fecha);
             rec = rec +
                   v.getPrecio()*v.getNumPasajeros();
             mp.put(fecha, rec);
         }
         else{
             Double rec =v.getPrecio()*v.getNumPasajeros();
             mp.put(fecha, rec);
         }
     }
     return mp;
}     

·         El segundo caso es si el tipo del conjunto de valores es una colección (List o Set), donde al ser tipos mutables no es necesario invocar al método put si la clave está en el Map. El esquema sería el siguiente sustituyendo Collection por List, Set o SortedSet:

Map<TipoPropiedad,Collection>  getElementosPorPropiedad(){
       Map<TipoPropiedad,Collection>mp=new HashMap<>();
       for(TipoBase e: lista_atributo){
             TipoPropiedad tp = e.getPropiedad();
             if (mp.containsKey(tp)){
                    mp.get(tp).add(e);
             }
             else{
                    Collection lb = new Constructor<>();
                    lb.add(e);
                    mp.put(tp,lb);            
             }
       }
       return mp;
}     

               
                Por ejemplo, un Map que relacione cada fecha con la lista de vuelos de ese día:

 
public Map<LocalDate,List<Vuelo>> getVuelosPorFecha(){
    Map<LocalDate,List<Vuelo>> mp = new HashMap<>();
    for(Vuelo v: vuelos){
       LocalDate d = v.getFecha();
       if (mp.containsKey(d)){
             mp.get(d).add(v);  
       }
       else{
           List<Vuelo> lv = new ArrayList<>();
           lv.add(v);
           mp.put(d,lv);
       }
   }
   return mp;
}     

 

4. Como crear un Map desde otros Map

En este caso, la algorítmica es más simple porque ya tenemos construido el conjunto de claves y el nuevo Map lo que hace es asignar a cada clave una operación sobre el valor del map de partida. Para poder recorrer las claves se usa el método keySet. El esquema sería el siguiente:

Map<TipoPropiedad, Cuantificador> getCuantificadorPorPropiedad(){
       Map <TipoPropiedad,Cuantificador>mp=new HashMap<>();
       Map> mporig=getMapOrigen();
       for(TipoPropiedad e: mporig.keySet()){
             Collection ctp = mporig.get(e);
             Cuantificador ct = getOperacion(ctp);
             mp.put(e,ct);
       }
       return mp;
}     

 
Por ejemplo, supongamos que queremos obtener un Map que relacione cada fecha con el vuelo más caro de ese día, a partir del método getVuelosPorFecha anterior:

public Map<LocalDate,Vuelo> getVueloMasCaroPorFecha(){
   Map <LocalDate,Vuelo>mp = new HashMap<>();
   Map<LocalDate,List<Vuelo>> mporig=getVuelosPorFecha();
   for (LocalDate f:mporig.keySet()){
      List lv = mporig.get(f);
      Vuelo maxv=Collections.max(lv, new ComparadorVueloPrecio());
      mp.put(f, maxv);
   }
   return mp;
}

O por ejemplo la recaudación media por fecha, usando getRecaudacionPorFecha y getContadorPorFecha:

public Map<LocalDate,Double> getRecaudacionMediaPorFecha(){
    Map<LocalDate,Double> mp = new HashMap<>();
    Map<LocalDate,Integer> mpcont=getContadorVuelosPorFecha();
    Map<LocalDate,Double> mprec=getRecaudacionPorFecha();
    for (LocalDate f:mprec.keySet()){
      Double recm=mprec.get(f)/mpcont.get(f);
      mp.put(f, recm);
    }
    return mp;
}

 
5. Como crear un Map invirtiendo otro

En algunos casos es útil poder “invertir” un Map. Es decir, que el conjunto de valores y el conjunto de claves intercambien sus papeles. El primer problema es que al ser un Map una aplicación sobreyectiva, puede haber dos claves con el mismo valor y al invertir la aplicación a cada nueva clave le puede corresponder varios valores. Por ejemplo, supongamos que tenemos un Map que relaciona los destinos con el número de vuelos a ese destino. Si hay 8 vuelos a Madrid y 8 vuelos a Barcelona, tendríamos que en el Map original hay una relación (Madrid, 8) y (Barcelona, 8). Al invertirlo, el número 8 pasa a ser clave del nuevo Map y sus valores son dos: Madrid y Barcelona, pero los Map no permiten claves repetidas, así que la solución es permitir que en el conjunto de valores haya una lista de destinos (las claves del Map original). Esto es, el nuevo Map inverso será >.

El invertir un Map sirve para responder preguntas del tipo ¿Cuál es el destino con más vuelos? ¿Cuál es el día con más pasajeros? ¿Cuál es la fecha con menor recaudación?

El código del método invierteMap se proporciona como en método de utilidad genérico. El Map invertido se suele devolver como un SortedMap que permita realizar más fácilmente operaciones de máximos y mínimos:

public static<K,T>SortedMap<T,List&lt;K>> invierteMap(Map<K,T> m){
   SortedMap<T,List&lt;K>> mapinv= new TreeMap>();
   Set<Kgt; conj=m.keySet();
            
   for (T ti: conj){
        K ki= m.get(ti);
        if (mapinv.containsKey(ki)){
              mapinv.get(ki).add(ti);
        }
        else{
              List<Kgt; lt = new ArrayList();
              lt.add(ti);
              mapinv.put(ki,lt);
       }
   }
   return mapinv;     
}

¿Cuándo y cómo usar el método invierteMap? Generalmente se usará invierteMap cuándo se quiera encontrar un máximo o mínimo de una propiedad sobre otra. Por ejemplo, si se quiere responder a cuál es el destino con más vuelos, cuál es la fecha con más pasajeros o cuál es la fecha de menor recaudación. Hay que tener en cuenta, que estás preguntas no son fáciles de responder por alguno de los métodos vistos hasta ahora, ya que no son máximos o mínimos directamente sobre los objetos de una colección como cuál es el vuelo más caro o el vuelo con menos pasajeros. Para responder a la pregunta de cuál es la fecha con más vuelos, debemos partir de la información disponible en el map contador Map<LocalDate, Integer> que se obtiene mediante el método getContadorVuelosPorFecha. Al invertir este Map se obtiene un SortedMap<Integer, List<LocalDate>>, donde a cada número de vuelos le corresponde la lista de fechas con ese número de vuelos, ya que puede haber varias fechas con el mismo número de vuelos. Al ser el map inverso un SortedMap nos permite obtener los valores menor o mayor para el conjunto de claves mediante los métodos firstKey o lastKey. Al obtener el valor correspondiente mediante el método get se responde a la pregunta, que realmente debería ser ¿Cuáles son las fechas con mayor número de vuelos?

public List<LocalDate> getFechasMayorNumVuelo(){
       Map<LocalDate,Integer> mp = getContadorVuelosPorFecha();
       SortedMap<Double, List<LocalDate>> mpinv = Utiles.invierteMap(mp);
       return mpinv.get(mpinv.lastKey());
}

De igual manera podemos obtener las fechas de menor recaudación invirtiendo el Map del método getRecaudacionPorFecha:

public List<LocalDate> getFechasMenorRecaudacion(){
     Map<LocalDate,Double>  mp = getRecaudacionPorFecha();
     SortedMap<Double,<LocalDate>> mpinv = Utiles.invierteMap(mp);
     return mpinv.get(mpinv.firstKey());
}

No hay comentarios:

Publicar un comentario