Parking con tarifas por tipo de vehículo
Gestiona las entradas y salidas de un parking con plazas para coches, motos y furgonetas: matrículas validadas, plazas por tipo, tarifa por cuarto de hora con 10 minutos gratis y máximo diario, y la recaudación del día. Herencia, clases abstractas, polimorfismo y mapas.
- Clases abstractas
- Herencia y @Override
- Polimorfismo
- HashMap
- switch con flechas
- Expresiones regulares
Enunciado
El aparcamiento de un centro comercial tiene 4 plazas para coches, 2 para motos y 1 para furgonetas (es una versión de prueba: el real tiene cientos). La barrera de entrada y la de salida envían al programa una línea por cada vehículo que pasa.
Cada tipo de vehículo paga distinto, pero todos con la misma regla: los 10 primeros minutos son gratis (para quien entra y se va) y, a partir de ahí, se cobra cada cuarto de hora empezado, con un máximo por día. Esa regla común es justo lo que debe ir en una clase padre, y lo que cambia (el precio y el máximo), en cada clase hija.
Las cámaras leen las matrículas en el formato actual español: cuatro cifras y tres consonantes, sin vocales, ni Ñ ni Q, para evitar palabras y confusiones.
Qué tiene que hacer el programa
- Cada línea es una orden:
ENTRA HH:MM tipo matrícula,SALE HH:MM matrículauOCUPACION. La orden, el tipo y la matrícula pueden venir en mayúsculas o minúsculas (la matrícula se guarda y se muestra en mayúsculas). Una línea con un número de palabras distinto del de su orden escribeOrden incompleta; una orden desconocida,Orden desconocida. Las líneas vacías se ignoran. ENTRAcomprueba, en este orden: la hora (Hora no válida: X, de 00:00 a 23:59), el tipo (Tipo no válido: X; soncoche,motoyfurgoneta), la matrícula (Matrícula no válida: X: 4 cifras y 3 letras de BCDFGHJKLMNPRSTVWXYZ), que no esté ya dentro (X ya está dentro) y que queden plazas de su tipo (No quedan plazas de tipo). Si todo va bien:Entra X (tipo) a las HH:MM.SALEcomprueba la hora, que el vehículo esté dentro (X no está en el parking) y que la salida no sea anterior a la entrada (La hora de salida es anterior a la entrada). Si todo va bien, el vehículo deja libre su plaza y se escribeSale X (tipo) tras D: I, con la duración como2 h 35 min,3 ho8 miny el importe en euros (6,60 €).- Tarifas: hasta 10 minutos, 0 €. Después, por cada 15 minutos empezados: coche 0,60 €, moto 0,30 € y furgoneta 0,90 €, con un máximo de 18 €, 9 € y 25 € respectivamente. Todas las estancias son del mismo día.
OCUPACIONescribeOcupación: coche 2/4 · moto 0/2 · furgoneta 1/1.- Al terminar la entrada:
Recaudación: X (N salidas)(«salida» si es 1) ySiguen dentro: …con las matrículas que no han salido en orden alfabético, separadas por coma y espacio, oninguno.
Entrada
Una orden por línea: ENTRA HH:MM tipo matrícula, SALE HH:MM matrícula u OCUPACION.
Datos de referencia
| Tipo | Plazas | Cada 15 min empezados | Máximo diario |
|---|---|---|---|
| coche | 4 | 0,60 € | 18,00 € |
| moto | 2 | 0,30 € | 9,00 € |
| furgoneta | 1 | 0,90 € | 25,00 € |
Ejemplos de ejecución
Tu programa debe escribir exactamente esta salida para estas entradas. Las pruebas del editor incluyen estos ejemplos y otros casos ocultos.
Un día de parking
Entrada
ENTRA 08:15 coche 1234BCD ENTRA 08:20 moto 5678FGH ENTRA 08:25 furgoneta 9012JKL ENTRA 08:30 furgoneta 3456MNP OCUPACION SALE 08:28 5678FGH SALE 10:50 1234BCD ENTRA 11:00 coche 1234bcd SALE 19:45 9012JKL SALE 12:00 0000BBB
Salida por consola
Entra 1234BCD (coche) a las 08:15 Entra 5678FGH (moto) a las 08:20 Entra 9012JKL (furgoneta) a las 08:25 No quedan plazas de furgoneta Ocupación: coche 1/4 · moto 1/2 · furgoneta 1/1 Sale 5678FGH (moto) tras 8 min: 0,00 € Sale 1234BCD (coche) tras 2 h 35 min: 6,60 € Entra 1234BCD (coche) a las 11:00 Sale 9012JKL (furgoneta) tras 11 h 20 min: 25,00 € 0000BBB no está en el parking Recaudación: 31,60 € (3 salidas) Siguen dentro: 1234BCD
Datos no válidos
Entrada
ENTRA 25:00 coche 1234BCD ENTRA 09:00 bici 1234BCD ENTRA 09:00 coche 1234ABC ENTRA 09:00 coche 1234BC ENTRA 09:05 moto 1111BBB ENTRA 09:06 moto 1111BBB SALE 09:00 1111BBB ENTRA ESTADO
Salida por consola
Hora no válida: 25:00 Tipo no válido: bici Matrícula no válida: 1234ABC Matrícula no válida: 1234BC Entra 1111BBB (moto) a las 09:05 1111BBB ya está dentro La hora de salida es anterior a la entrada Orden incompleta Orden desconocida Recaudación: 0,00 € (0 salidas) Siguen dentro: 1111BBB
Guía paso a paso
Intenta resolverlo por tu cuenta y abre un paso solo cuando te atasques: cada uno te acerca a la solución sin dártela entera.
1. La clase abstracta Vehiculo
Pon en Vehiculo lo común: la matrícula, la hora de entrada y el cálculo del importe. Lo que cambia de un tipo a otro son dos números, así que decláralos como métodos abstractos que cada subclase tendrá que implementar.
abstract long precioFraccion();
abstract long maximoDiario();
long importe(int minutos) {
if (minutos <= 10) return 0;
long fracciones = (minutos + 14) / 15; // cuartos de hora empezados
return Math.min(fracciones * precioFraccion(), maximoDiario());
}2. Las subclases
Coche, Moto y Furgoneta extienden Vehiculo, llaman a super(matricula, entrada) en su constructor y solo devuelven su tipo, su precio y su máximo. importe no se repite: lo heredan.
3. Los vehículos aparcados
Un HashMap<String, Vehiculo> con la matrícula como clave responde al instante si un vehículo está dentro y te lo devuelve al salir. Las plazas ocupadas de un tipo se cuentan recorriendo dentro.values().
4. Las horas como minutos
Pasa cada hora a minutos desde las 00:00: así la duración es una resta. Una expresión regular comprueba el formato y el rango de una vez.
if (!hora.matches("([01]\\d|2[0-3]):[0-5]\\d")) return -1;
return Integer.parseInt(hora.substring(0, 2)) * 60 + Integer.parseInt(hora.substring(3));5. El polimorfismo en la salida
Al salir, v.importe(estancia) se llama igual para cualquier vehículo, pero cada objeto usa el precio de su clase. Tu código no necesita ningún if (tipo.equals("moto")) para cobrar.
Resuélvelo aquí
El editor trae el esqueleto del programa. Pulsa «Ejecutar» para comprobarlo con los ejemplos y con 2 casos ocultos que buscan los errores típicos.
Ejemplo
ENTRA 08:15 coche 1234BCD ENTRA 08:20 moto 5678FGH ENTRA 08:25 furgoneta 9012JKL ENTRA 08:30 furgoneta 3456MNP OCUPACION SALE 08:28 5678FGH SALE 10:50 1234BCD ENTRA 11:00 coche 1234bcd SALE 19:45 9012JKL SALE 12:00 0000BBB
Entra 1234BCD (coche) a las 08:15 Entra 5678FGH (moto) a las 08:20 Entra 9012JKL (furgoneta) a las 08:25 No quedan plazas de furgoneta Ocupación: coche 1/4 · moto 1/2 · furgoneta 1/1 Sale 5678FGH (moto) tras 8 min: 0,00 € Sale 1234BCD (coche) tras 2 h 35 min: 6,60 € Entra 1234BCD (coche) a las 11:00 Sale 9012JKL (furgoneta) tras 11 h 20 min: 25,00 € 0000BBB no está en el parking Recaudación: 31,60 € (3 salidas) Siguen dentro: 1234BCD
Solución explicada
Ver la solución completa
1import java.util.ArrayList;
2import java.util.HashMap;
3import java.util.List;
4import java.util.Locale;
5import java.util.Map;
6import java.util.Scanner;
7
8abstract class Vehiculo {
9 private final String matricula;
10 private final int entrada; // minutos desde las 00:00
11
12 Vehiculo(String matricula, int entrada) {
13 this.matricula = matricula;
14 this.entrada = entrada;
15 }
16
17 String getMatricula() { return matricula; }
18 int getEntrada() { return entrada; }
19
20 abstract String tipo();
21 /** Céntimos por cada 15 minutos empezados. */
22 abstract long precioFraccion();
23 /** Lo máximo que se cobra en un día, en céntimos. */
24 abstract long maximoDiario();
25
26 /** Importe en céntimos de una estancia: 10 minutos gratis y, después, cada cuarto de hora empezado, con un máximo. */
27 long importe(int minutos) {
28 if (minutos <= 10) return 0;
29 long fracciones = (minutos + 14) / 15;
30 return Math.min(fracciones * precioFraccion(), maximoDiario());
31 }
32}
33
34class Coche extends Vehiculo {
35 Coche(String matricula, int entrada) { super(matricula, entrada); }
36 String tipo() { return "coche"; }
37 long precioFraccion() { return 60; }
38 long maximoDiario() { return 1800; }
39}
40
41class Moto extends Vehiculo {
42 Moto(String matricula, int entrada) { super(matricula, entrada); }
43 String tipo() { return "moto"; }
44 long precioFraccion() { return 30; }
45 long maximoDiario() { return 900; }
46}
47
48class Furgoneta extends Vehiculo {
49 Furgoneta(String matricula, int entrada) { super(matricula, entrada); }
50 String tipo() { return "furgoneta"; }
51 long precioFraccion() { return 90; }
52 long maximoDiario() { return 2500; }
53}
54
55public class Main {
56 static final List<String> TIPOS = List.of("coche", "moto", "furgoneta");
57 static final Map<String, Integer> PLAZAS = Map.of("coche", 4, "moto", 2, "furgoneta", 1);
58
59 static final Map<String, Vehiculo> dentro = new HashMap<>();
60 static long recaudado = 0;
61 static int salidas = 0;
62
63 /** «08:15» → 495 minutos; -1 si no es una hora válida. */
64 static int minutos(String hora) {
65 if (!hora.matches("([01]\\d|2[0-3]):[0-5]\\d")) return -1;
66 return Integer.parseInt(hora.substring(0, 2)) * 60 + Integer.parseInt(hora.substring(3));
67 }
68
69 static String euros(long centimos) {
70 return String.format(Locale.ROOT, "%d,%02d €", centimos / 100, centimos % 100);
71 }
72
73 static String duracion(int minutos) {
74 int h = minutos / 60, m = minutos % 60;
75 if (h == 0) return m + " min";
76 return m == 0 ? h + " h" : h + " h " + m + " min";
77 }
78
79 static int ocupadas(String tipo) {
80 int n = 0;
81 for (Vehiculo v : dentro.values()) if (v.tipo().equals(tipo)) n++;
82 return n;
83 }
84
85 static void entra(String hora, String tipo, String matricula) {
86 int min = minutos(hora);
87 if (min < 0) { System.out.println("Hora no válida: " + hora); return; }
88 if (!TIPOS.contains(tipo)) { System.out.println("Tipo no válido: " + tipo); return; }
89 // Matrículas españolas: 4 cifras y 3 consonantes (sin vocales, Ñ ni Q)
90 if (!matricula.matches("\\d{4}[BCDFGHJKLMNPRSTVWXYZ]{3}")) { System.out.println("Matrícula no válida: " + matricula); return; }
91 if (dentro.containsKey(matricula)) { System.out.println(matricula + " ya está dentro"); return; }
92 if (ocupadas(tipo) == PLAZAS.get(tipo)) { System.out.println("No quedan plazas de " + tipo); return; }
93
94 Vehiculo v = switch (tipo) {
95 case "coche" -> new Coche(matricula, min);
96 case "moto" -> new Moto(matricula, min);
97 default -> new Furgoneta(matricula, min);
98 };
99 dentro.put(matricula, v);
100 System.out.println("Entra " + matricula + " (" + tipo + ") a las " + hora);
101 }
102
103 static void sale(String hora, String matricula) {
104 int min = minutos(hora);
105 if (min < 0) { System.out.println("Hora no válida: " + hora); return; }
106 Vehiculo v = dentro.get(matricula);
107 if (v == null) { System.out.println(matricula + " no está en el parking"); return; }
108 if (min < v.getEntrada()) { System.out.println("La hora de salida es anterior a la entrada"); return; }
109
110 int estancia = min - v.getEntrada();
111 long importe = v.importe(estancia); // cada tipo de vehículo aplica su tarifa
112 dentro.remove(matricula);
113 recaudado += importe;
114 salidas++;
115 System.out.println("Sale " + matricula + " (" + v.tipo() + ") tras " + duracion(estancia) + ": " + euros(importe));
116 }
117
118 public static void main(String[] args) {
119 Scanner sc = new Scanner(System.in);
120 while (sc.hasNextLine()) {
121 String linea = sc.nextLine().trim();
122 if (linea.isEmpty()) continue;
123 String[] p = linea.split("\\s+");
124 switch (p[0].toUpperCase()) {
125 case "ENTRA" -> {
126 if (p.length != 4) System.out.println("Orden incompleta");
127 else entra(p[1], p[2].toLowerCase(), p[3].toUpperCase());
128 }
129 case "SALE" -> {
130 if (p.length != 3) System.out.println("Orden incompleta");
131 else sale(p[1], p[2].toUpperCase());
132 }
133 case "OCUPACION" -> {
134 List<String> partes = new ArrayList<>();
135 for (String tipo : TIPOS) partes.add(tipo + " " + ocupadas(tipo) + "/" + PLAZAS.get(tipo));
136 System.out.println("Ocupación: " + String.join(" · ", partes));
137 }
138 default -> System.out.println("Orden desconocida");
139 }
140 }
141
142 System.out.println("Recaudación: " + euros(recaudado) + " (" + salidas + (salidas == 1 ? " salida)" : " salidas)"));
143 List<String> quedan = new ArrayList<>(dentro.keySet());
144 quedan.sort(null);
145 System.out.println("Siguen dentro: " + (quedan.isEmpty() ? "ninguno" : String.join(", ", quedan)));
146 }
147}La clase abstracta recoge la regla de cobro, que es la misma para todos, y deja abiertos solo los dos datos que cambian. Cada subclase es pequeña y declarativa: añadir un tipo nuevo (por ejemplo, autocaravana) sería escribir una clase de cinco líneas y añadirla al switch de creación.
El único lugar donde aparece el tipo como texto es al crear el objeto. A partir de ahí, el programa trabaja con Vehiculo y el polimorfismo elige el precio correcto: es la idea central de la herencia.
La fórmula (minutos + 14) / 15 es el redondeo hacia arriba de una división entera: 16 minutos dan 2 cuartos de hora, 15 dan 1. Combinada con Math.min aplica el máximo diario sin un if más.
Las comprobaciones se hacen en métodos con return temprano: cada error sale en cuanto se detecta, y el camino normal queda al final sin anidar condiciones.
Para ir más allá
- Admite estancias de varios días con fecha y hora, aplicando el máximo a cada día.
- Añade abonados (una lista de matrículas que no pagan) con una clase o un atributo, y cuenta sus entradas aparte.
- Guarda el histórico de salidas y añade
INFORMEcon la recaudación por tipo de vehículo.