Manejo de colecciones de objetos¶
Vamos a ver a continuación algunos ejemplos de retos donde es necesario (o recomendable) almacenar la información de forma más compleja, empleando clases y objetos ya que hay varios datos que tratar de cada individuo del problema. También, en algunos de estos casos, será necesario ordenar la colección de datos siguiendo algún criterio para resolver el problema.
1. Ordenando listas de objetos: los niños buenos¶
En este reto de Acepta el Reto nos plantean que resolvamos cómo va a repartir Papá Noel los regalos a los niños. De cada niño tenemos dos datos: cómo de bien se ha portado (número entre 1 y 100) y cuánto pesan los regalos que ha pedido (número entre 1 y 1000). Papá Noel quiere repartir primero los regalos a los niños que mejor se han portado y, si dos niños se han portado igual, quiere repartir primero los regalos que pesen menos.
Cada caso de prueba vendrá iniciado por un número n, que indica a cuántos niños hay que repartir regalos. Luego vienen los datos de cada niño en una línea; de cada uno guardaremos dos datos: cómo de bueno ha sido y cuánto pesan los regalos que ha pedido. Deberemos mostrar por pantalla los datos ordenados de los niños, en el orden en que Papá Noel repartirá los regalos.
Por ejemplo, si tenemos este caso de entrada:
3
80 2
100 12
100 1
Tendremos que procesar en total 3 niños. Primero se repartirán los regalos a los niños que se han portado 100 de bien y, ante esto, se elegirá primero al que sus regalos pesan 1, y luego al que pesan 12. Se deja en tercer lugar al niño que se ha portado 80 de bien. Así, la salida para este caso debería ser:
100 1
100 12
80 2
Para empezar, crearemos una clase Nino
que almacenará como atributos los datos de cada niño: cómo de bueno ha sido (atributo bueno
) y cuánto pesan sus regalos (atributo peso
). En el constructor asignamos las dos cosas, y hacemos que la clase implemente la interfaz Comparable<Nino>
para poder comparar dos niños entre sí. Lo que haremos será comparar el atributo bueno
de mayor a menor y, si son iguales, entonces comparamos el atributo peso
de menor a mayor. Añadimos también un método toString
para sacar la información del niño con el formato que pide el problema (bueno
y peso
separados por un espacio). Así quedaría esta clase:
class Nino implements Comparable<Nino>
{
private int bueno;
private int peso;
public Nino(int bueno, int peso)
{
this.bueno = bueno;
this.peso = peso;
}
@Override
public int compareTo(Nino n)
{
// Comparamos "bueno" de mayor a menor
int resultado = Integer.compare(n.bueno, this.bueno);
// Si coincide, comparamos "peso" de menor a mayor
if(resultado == 0)
{
resultado = Integer.compare(this.peso, n.peso);
}
return resultado;
}
@Override
public String toString()
{
return bueno + " " + peso;
}
}
Ahora que ya tenemos esta clase, podemos construir un programa principal que lea cada caso de prueba, construya una lista de objetos Nino
con los datos que vaya leyendo de cada línea, la ordene según el criterio que hemos definido en la propia clase y la saque por pantalla:
public class Reto366
{
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
ArrayList<Nino> lista;
int n;
do
{
// Leemos el número de niños
n = sc.nextInt();
if(n != 0)
{
// Almacenamos los niños en una lista
lista = new ArrayList<Nino>();
for(int i = 1; i <= n; i++)
{
lista.add(new Nino(sc.nextInt(), sc.nextInt()));
}
// Ordenamos por el criterio de la clase Nino
Collections.sort(lista);
for(Nino nino : lista)
{
System.out.println(nino);
}
// Debemos sacar una línea en blanco al final de cada caso
System.out.println();
}
}
while(n != 0);
}
}
1.1. Desarrollos en las bicicletas¶
En este reto de Acepta el Reto debemos hacer algo parecido con los desarrollos de las bicicletas que nos plantean. Cada caso de prueba consta de tres líneas:
- En la primera línea nos dicen cuántos platos p y cuántas coronas c tiene la bicicleta en cuestión
- En la segunda línea nos dan los tamaños de los platos, separados por espacios
- En la tercera línea nos dan los tamaños de las coronas, separados por espacios
La idea es crear una clase Desarrollo
que almacene una combinación de tamaño de plato y corona, y en el programa principal una lista de desarrollos que comprueba todas las combinaciones de platos y coronas que se puede tener con la bicicleta, y las ordene de menor a mayor desarrollo (es decir, de menor a mayor relación entre tamaño de plato y de corona).
Por ejemplo, para este caso de prueba:
2 3
34 48
20 26 34
Tenemos 2 platos de tamaño 34 y 48, y 3 coronas de tamaño 20, 26 y 34. Con esto salen 6 posibles combinaciones:
- Plato 34, Corona 20
- Plato 34, Corona 26
- Plato 34, Corona 34
- Plato 48, Corona 20
- Plato 48, Corona 26
- Plato 48, Corona 34
La combinación que menos desarrollo tiene es la primera (34 / 34), y la que más tiene es la cuarta (48 / 20). Ordenadas de menor a mayor con el formato que pide el ejercicio (mostrando plato y corona separados por un guión, todos en una línea separados por espacios) quedaría así:
34-34 34-26 48-34 34-20 48-26 48-20
Ejercicio 1
Trata de resolver este reto en Java y comprueba que la plataforma lo acepta.
2. Uso de diccionarios: el telegrama más corto¶
Vamos a practicar cómo emplear diccionarios o mapas en retos con este reto de Acepta el Reto. En él nos dan al principio una tabla de cómo se codifican los distintos símbolos en código Morse, empleando puntos y rayas. Debemos procesar una serie de casos de pruebas, cada uno formado por un texto, y debemos indicar cuál es la duración en puntos totales del telegrama transcrito a Morse. Para ello se deben tener en cuenta las siguientes premisas:
- Cada raya que se emita equivale a 3 puntos
- Entre cada símbolo (punto o raya) de una misma letra hay que esperar 1 punto
- Entre cada letra o símbolo del mensaje hay que esperar 3 puntos
- Entre cada palabra (es decir, cada espacio en blanco que nos encontremos) hay que esperar 5 puntos
Así, por ejemplo:
- Para el mensaje "?", cuyo código Morse es
..--..
, tendremos en total:- 1 + 1 + 3 + 3 + 1 + 1 = 10 puntos de los puntos y rayas del mensaje
- 5 puntos, uno por la separación entre cada uno de los 6 símbolos anteriores
- Total: 15 puntos
- Para el mensaje "YA NACIO" tendremos el siguiente cómputo
- Y =
-.--
= 10 puntos + 3 de separación = 13 puntos - A =
.-
= 4 puntos + 1 de separación = 5 puntos - (espacio en blanco) = 5 puntos
- N =
-.
= 4 puntos + 1 de separación = 5 puntos - A =
.-
= 4 puntos + 1 de separación = 5 puntos - C =
-.-.
= 8 puntos + 3 de separación = 11 puntos - I =
..
= 2 puntos + 1 de separación = 3 puntos - O =
---
= 9 puntos + 2 de separación = 11 puntos - Además, contamos 3 puntos de separación entre cada letra: Y-A, N-A, A-C, C-I e I-O (15 en total)
- Suma total = 73 puntos
Comenzaremos almacenando en un diccionario o tabla hash la codificación de cada símbolo en su cadena Morse equivalente. Esto lo haremos con la siguiente función:
static HashMap<Character, String> crearMapaMorse()
{
HashMap<Character, String> tabla = new HashMap<>();
tabla.put('A', ".-");
tabla.put('B', "-...");
tabla.put('C', "-.-.");
tabla.put('D', "-..");
tabla.put('E', ".");
tabla.put('F', "..-.");
tabla.put('G', "--.");
tabla.put('H', "....");
tabla.put('I', "..");
tabla.put('J', ".---");
tabla.put('K', "-.-");
tabla.put('L', ".-..");
tabla.put('M', "--");
tabla.put('N', "-.");
tabla.put('O', "---");
tabla.put('P', ".--.");
tabla.put('Q', "--.-");
tabla.put('R', ".-.");
tabla.put('S', "...");
tabla.put('T', "-");
tabla.put('U', "..-");
tabla.put('V', "...-");
tabla.put('W', ".--");
tabla.put('X', "-..-");
tabla.put('Y', "-.--");
tabla.put('Z', "--..");
tabla.put('!', "-.-.--");
tabla.put('?', "..--..");
return tabla;
}
En nuestro programa principal leeremos primero el número de casos de prueba, y luego leemos el texto de cada caso y vamos analizando símbolo a símbolo:
public static void main(String[] args)
{
HashMap<Character, String> tabla = crearMapaMorse();
String linea;
Scanner sc = new Scanner(System.in);
// Leemos los casos y el salto de línea que hay después
int casos = sc.nextInt();
sc.nextLine();
for(int n = 1; n <= casos; n++)
{
// Leemos cada caso
linea = sc.nextLine();
int total = 0;
// Procesamos cada símbolo del texto leído
for(int i = 0; i < linea.length(); i++)
{
if(linea.charAt(i) != ' ')
{
// Obtenemos la representación Morse de la letra
String representacion = tabla.get(linea.charAt(i));
for(int j = 0; j < representacion.length(); j++)
{
if(representacion.charAt(j) == '.')
total += 1;
else
total += 3;
}
// Un punto entre cada símbolo
total += representacion.length() - 1;
if (i > 0 && linea.charAt(i-1) != ' ')
{
// 3 puntos entre cada letra
total += 3;
}
}
else
{
// 5 puntos entre cada palabra
total += 5;
}
}
System.out.println(total);
}
}
2.2. Ninots indultados¶
En este reto de Acepta el Reto debemos determinar qué ninots son indultados en las fallas de Valencia. Cada caso de prueba vendrá dado por un número n de votaciones de ninots, y luego seguirán n palabras (dispuestas en varias líneas, y separadas por espacios) con los nombres de los ninots votados (en mayúsculas si son de fallas adultas, en minúsculas si son infantiles). Debemos indicar en una línea, y separados por espacios, los nombres de los ninots infantil y adulto que han recibido más votos.
Por ejemplo, para este caso de prueba:
5
condealtea GUANYARDINES
antigadecampanar QUARTEXTRAMURS GUANYARDINES
Deberemos indicar que en el caso de las fallas infantiles hay "empate", y en el de las adultas se indulta a GUANYARDINES:
empate GUANYARDINES
Aquí van algunas PISTAS:
- Para leer cada palabra por separado de las siguientes puede usar el método
next()
deScanner
(sc.next()
), que va procesando cada token o palabra clave que encuentra. Es conveniente que la enlaces con una operacióntrim
para eliminar espacios que puedan quedar a ambos lados:sc.next().trim()
. - Puedes crear un diccionario o mapa para guardarte las ocurrencias de las fallas infantiles y otra para las adultas (o un solo diccionario para todas). Cada vez que leas una palabra, miras si está en el diccionario: si no está la añades con contador 1, y si está le incrementas el contador. Deberás crear para ello un diccionario cuyas claves sean Strings (nombres de las fallas) y cuyos valores sean enteros (
Integer
en Java).
Ejercicio 2
Trata de resolver este reto en Java y comprueba que la plataforma lo acepta.
3. Uso de conjuntos: ¿Podemos empezar?¶
En este reto de Acepta el Reto debemos decidir si puede empezar o no una reunión de vecinos. Para ello, en cada caso de prueba nos darán dos líneas:
- En la primera nos dan tres números indicando cuántos pisos hay en la urbanización (p), cuántas letras o puertas hay en cada piso (l) y cuántos asistentes han venido a la reunión (a).
- En la segunda línea nos dan, separados por espacios, el piso y la letra donde vive cada vecino. Puede haber más de un vecino que viva en el mismo piso y letra y hayan venido juntos a la reunión
Deberemos mostrar el mensaje "EMPEZAMOS" si al menos hay vecinos de la mitad de viviendas, o "ESPERAMOS" si aún no. En el caso de que el total de viviendas sea impar, se considerará la mitad por exceso.
Por ejemplo, dado este caso de prueba:
4 1 3
1 A 2 A 1 A
La casa tiene 4 plantas, con una letra por planta, y han venido 3 vecinos que son dos del 1ºA y otro del 2ºA. En total se han cubierto 2 de las 4 viviendas del edificio, con lo que podemos empezar la reunión.
Para resolver el reto haremos lo siguiente:
- Calcularemos cuántas viviendas totales hay en el edificio. Esto se consigue multiplicando los dos primeros datos que nos dan: el número de pisos p por el total de letras en cada piso l.
- Almacenamos en un conjunto las distintas viviendas que vienen a la reunión, de forma que descartamos las viviendas repetidas. Si el tamaño del conjunto es al menos la mitad del total, podremos empezar (considerando también el caso de que haya un número impar de viviendas)
Aquí tenemos una posible solución en Java:
import java.util.*;
public class PodemosEmpezar
{
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
HashSet<String> viviendas;
int pisos, letras, asistentes, totalViviendas, mitad;
do
{
pisos = sc.nextInt();
letras = sc.nextInt();
asistentes = sc.nextInt();
if (pisos != 0)
{
// Calculamos el total de viviendas
totalViviendas = pisos * letras;
viviendas = new HashSet<>();
// Añadimos en el conjunto el número de piso seguido de la letra
for (int i = 0; i < asistentes; i ++)
{
String piso = sc.next();
String letra = sc.next();
viviendas.add(piso + letra);
}
// Calculamos la mitad de asistentes necesaria
if (totalViviendas % 2 == 0)
mitad = totalViviendas / 2;
else
mitad = totalViviendas / 2 + 1;
if (viviendas.size() >= mitad)
System.out.println("EMPEZAMOS");
else
System.out.println("ESPERAMOS");
}
} while (pisos != 0);
}
}
En esta ocasión también empleamos sc.next()
para ir leyendo los pisos y letras de los asistentes a la reunión, de forma consecutiva.
3.1. Michael J. Fox y el Pato Donald¶
En este reto de Acepta el Reto tenemos que distinguir si hay cumpleaños repetidos en una secuencia de fechas. Nos darán para cada caso de prueba el número de fechas que tenemos que procesar, y luego una secuencia de fechas, separadas por espacios. Tenemos que indicar si hay cumpleaños repetidos (SI) o todos son distintos (NO).
Por ejemplo, para este caso de prueba:
5
9/6/1961 22/10/1938 31/5/1961 20/4/1964 9/6/1934
Vemos que el cumpleaños el 9/6 se repite dos veces, por lo que diremos que SI.
Ejercicio 3
Trata de resolver este reto en Java y comprueba que la plataforma lo acepta.