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();
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;
}
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<K>> invierteMap(Map<K,T> m){
SortedMap<T,List<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