P0 — Calentamiento
Publicación: semana 1 · Entrega: semana 3 · Individual · Reglas
generales de entrega: projects/README.md.
Bodega es el sistema de inventario y ventas de una tienda de barrio, y lo van a
construir durante todo el semestre. Esta primera entrega es corta: tres
programas pequeños para retomar la programación desde lo que ya saben hacer en
C (leer datos, hacer cuentas, recorrer arreglos), ahora en Java, y la primera
clase del proyecto, Product, que en las entregas siguientes va a crecer.
Doña Rosa atiende una bodega en Surquillo. Vende arroz, leche evaporada, pan francés, gaseosas y huevos; cobra en soles, da vuelto y anota en un cuaderno lo que le queda en los estantes. Lo que escriban en P0 son las primeras piezas de lo que reemplazará a ese cuaderno.
Objetivos
- Escribir, compilar y ejecutar programas en archivos fuente compactos
(
void main()), leyendo de la entrada estándar conIO.readlny escribiendo conIO.println. - Manejar dinero sin errores de redondeo: céntimos en un
long, nuncadouble. - Escribir una primera clase con campos, un constructor y métodos de consulta y de modificación, y crear objetos de esa clase desde un programa.
- Trabajar con el
Makefiledel proyecto y leer la salida de las pruebas.
Lo que necesitan de las semanas 1 y 2
- Apuntes de la semana 1: archivos fuente compactos,
tipos primitivos,
Stringy arreglos y una primera clase. - Apuntes de la semana 2: campos,
métodos de instancia y
this, constructores y stack y heap (un arreglo de objetos es un arreglo de referencias). - Eck, sec. 3.8 Introduction to Arrays, sec. 5.1 Objects, Instance Methods, and Instance Variables y sec. 5.2 Constructors and Object Initialization.
- La clase
TransitCardde la semana 1 es un buen modelo paraProduct.
Recetas de la biblioteca estándar
Estas operaciones todavía no se vieron en clase. Úsenlas tal cual:
| Para | Escriban | Documentación |
|---|---|---|
Leer una línea (sin el salto de línea); devuelve null al final de la entrada |
String line = IO.readln(); |
IO |
Partir "2;4.20;Leche" en sus campos |
String[] f = line.split(";"); |
String.split |
| Quitar los espacios de los extremos | f[2].strip() |
String.strip |
| Convertir texto en número | Integer.parseInt("24"), Long.parseLong("4") |
Integer.parseInt, Long.parseLong |
| Buscar un carácter y cortar un texto | text.indexOf('.'), text.substring(0, i), text.substring(i + 1) |
String |
| Escribir céntimos como soles | String.format("S/ %d.%02d", cents / 100, cents % 100) da S/ 4.20 para 420 |
String.format |
String.format usa los mismos especificadores que printf en C: %02d
escribe un entero con al menos dos dígitos, rellenando con ceros.
Montos en soles
En los tres programas los montos se leen como texto en soles, de una de dos
formas: un entero (50, que es S/ 50.00) o un número con punto y exactamente
dos decimales (4.20, 0.30). Conviértanlos a céntimos (50 → 5000,
4.20 → 420) con indexOf, substring y Long.parseLong, sin pasar por
double, y escríbanlos siempre con la receta de String.format: S/ 4.20,
S/ 0.30, S/ 175.20 (sin separador de miles).
Pueden escribir los dos métodos auxiliares (leer soles, escribir soles) dentro
de cada archivo compacto, fuera de main: un archivo compacto puede tener
varios métodos. Sí, quedan repetidos en los tres programas; en P1 eso se
arregla con una clase.
En P0 pueden suponer que la entrada es válida: tiene el formato que dice cada requisito, los números son correctos y no negativos. Qué hacer con los datos inválidos es parte de P1.
Requisitos
La salida de los programas se compara carácter por carácter con la
esperada (ver Cómo se evalúa): no impriman mensajes para
pedir datos (IO.readln() sin argumento), ni espacios de más al final de las
líneas, y respeten mayúsculas, tildes y puntuación de los ejemplos.
1. MakeChange.java: el vuelto
Lee dos líneas: el total de la compra y el monto que entrega el cliente.
- Si el monto no alcanza, escribe una sola línea
Falta: S/ X.XXcon lo que falta, y termina. - Si alcanza, escribe
Vuelto: S/ X.XXy después cómo darlo con la menor cantidad de billetes y monedas, de mayor a menor, una línea por denominación que se use:N billete de S/ X.XXoN moneda de S/ X.XX, conbilletesymonedasen plural cuandoNes mayor que 1. - Denominaciones: billetes de S/ 200, 100, 50, 20 y 10; monedas de S/ 5, 2, 1, 0.50, 0.20 y 0.10. Para estas denominaciones, tomar siempre la más grande que quepa da la menor cantidad.
- La bodega no tiene monedas de menos de S/ 0.10. Si queda un resto, la
última línea es
Sin sencillo para: S/ 0.0X. - Si el vuelto es cero, se escribe solo
Vuelto: S/ 0.00.
Guarden las denominaciones en un arreglo (long[]) y recórranlo; no escriban
once if seguidos.
$ printf '23.40\n50\n' | java -cp build MakeChange
Vuelto: S/ 26.60
1 billete de S/ 20.00
1 moneda de S/ 5.00
1 moneda de S/ 1.00
1 moneda de S/ 0.50
1 moneda de S/ 0.10
2. PrintReceipt.java: la boleta
Lee líneas con el formato cantidad;precio;descripción hasta que llegue una
línea vacía o se acabe la entrada (IO.readln() devuelve null). Puede haber
espacios alrededor de cada campo, que se ignoran. La cantidad es un entero
positivo.
- Por cada línea escribe
cantidad x descripción @ S/ precio = S/ importe, donde el importe es cantidad por precio. - Al final escribe
Total: S/ X.XX (N artículos), dondeNes la suma de las cantidades; siNes 1,(1 artículo). - Si no hay ninguna línea, escribe solo
Boleta vacía.
$ printf '2;4.20;Leche evaporada\n1;6.50;Queso fresco 250 g\n10;0.30;Pan francés\n' | java -cp build PrintReceipt
2 x Leche evaporada @ S/ 4.20 = S/ 8.40
1 x Queso fresco 250 g @ S/ 6.50 = S/ 6.50
10 x Pan francés @ S/ 0.30 = S/ 3.00
Total: S/ 17.90 (13 artículos)
3. Product.java: la primera clase del proyecto
Una clase normal (no un archivo compacto), en su propio archivo, con este constructor y estos métodos públicos. Las pruebas los usan con estos nombres y tipos exactos:
| Miembro | Qué hace |
|---|---|
Product(String code, String name, long priceCents, int stock) |
Crea el producto. El código identifica al producto en la bodega ("LEC-01"); el precio es por unidad, en céntimos. |
String code(), String name(), long priceCents(), int stock() |
Devuelven los datos. |
void addStock(int quantity) |
Suma quantity unidades al stock (llegó mercadería). |
void removeStock(int quantity) |
Resta quantity unidades del stock (se vendieron o se malograron). |
long stockValueCents() |
Lo que vale todo el stock: precio por stock, en céntimos. |
- Los campos son
private. Fíjense en que los métodos de acceso se llamancode()ystock(), nogetCode(): es la convención de los records de Java, que usamos en todo el curso. - El precio va en céntimos y en un
long. Cuidado constockValueCents(): el producto de unlongpor uninteslong, pero el de dosintpuede dar overflow sin avisar (semana 1,FromC.java). Una de las pruebas lo verifica. - En P0 las cantidades siempre son válidas: positivas y, al descontar, no mayores que el stock. No validen nada todavía: en P1 la clase va a rechazar los datos inválidos, y las pruebas de P0 solo usan datos válidos para que sigan pasando entonces.
- No cambien esta firma en el resto del semestre. Las pruebas de P0 se vuelven a correr en cada entrega, hasta P5.
4. StockSummary.java: el resumen del stock
Lee un entero N (al menos 1) y después N líneas código;nombre;precio;stock
(con espacios opcionales alrededor de cada campo). Guarda cada producto como un
objeto Product en un arreglo Product[] de tamaño N y, recorriendo ese
arreglo, escribe:
- Una línea por producto, en el orden de la entrada:
código nombre: S u. x S/ precio = S/ valor, dondeSes el stock y el valor sale destockValueCents(). Valor del stock: S/ X.XX, la suma de los valores.Más valioso: código nombre, el producto con mayor valor de stock; si hay empate, el primero de la entrada.Por reponer (5 u. o menos): A, B, Ccon los códigos de los productos con stock de 5 o menos, en el orden de la entrada y separados por coma y espacio, oPor reponer (5 u. o menos): ninguno.
$ java -cp build StockSummary < tests/StockSummary.p0-1.in
ARR-02 Arroz superior 5 kg: 8 u. x S/ 21.90 = S/ 175.20
LEC-01 Leche evaporada: 5 u. x S/ 4.20 = S/ 21.00
ACE-01 Aceite vegetal 1 l: 12 u. x S/ 9.90 = S/ 118.80
PAN-01 Pan francés: 0 u. x S/ 0.30 = S/ 0.00
Valor del stock: S/ 315.00
Más valioso: ARR-02 Arroz superior 5 kg
Por reponer (5 u. o menos): LEC-01, PAN-01
El programa debe usar los métodos de Product (stock(),
stockValueCents(), …), no volver a calcular por su cuenta lo que la clase
ya sabe.
Archivos provistos
| Archivo | Para qué |
|---|---|
Makefile |
El Makefile del proyecto. Es el mismo para todas las entregas; no lo modifiquen. |
P0ProductPublicTest.java |
Pruebas públicas JUnit de Product. No las modifiquen. |
tests/ |
Casos públicos de los tres programas: cada Programa.p0-N.in es una entrada y Programa.p0-N.expected, la salida esperada. |
Product.java, MakeChange.java, PrintReceipt.java, StockSummary.java |
Esqueletos: compilan, pero no hacen nada. Los completan ustedes. |
Creen una carpeta para el proyecto; será la misma todo el semestre (si usan
git, el repositorio). Copien ahí todo lo de esta carpeta menos este
README.md:
mkdir -p ~/bodega
cp -R CURSO/projects/p0-calentamiento/. ~/bodega/
rm ~/bodega/README.md
cd ~/bodega
CURSO/env/run.sh make test # o abran la shell del contenedor y corran make test
CURSO es la carpeta del repositorio del curso. Con los esqueletos, make test compila y todas las pruebas fallan: ese es el punto de partida.
Cómo se prueba
Pruebas públicas
make test hace dos cosas:
- Para cada caso de
tests/, ejecuta el programa con esa entrada y compara su salida con la esperada usandodiff. EscribeokoFALLApor caso; si falla,diff -umuestra las líneas distintas: las que empiezan con-son las esperadas y las que empiezan con+, las que escribió su programa. - Corre las pruebas JUnit (
*Test.java), incluidas las suyas.
Para probar un programa a mano: java -cp build MakeChange y escriban la
entrada (terminen con Ctrl-d), o java -cp build MakeChange < tests/MakeChange.p0-1.in.
Hay 8 casos públicos de entrada y salida y 7 pruebas JUnit.
Pruebas privadas y diseño
Las pruebas privadas usan otras entradas con el mismo formato: otros montos (incluidos vueltos que usan billetes grandes y varias monedas iguales), boletas de una sola línea o con descripciones con espacios, y resúmenes con empates. El código, además, tiene que ser legible: nombres claros, métodos cortos, arreglos en lugar de código repetido, y que compile sin warnings.
Qué entregar
Para la entrega de la semana 3:
- Los cuatro archivos
.javacompletos y la carpetatests/sin cambios. - Al menos una prueba JUnit propia en
ProductTest.java(o varias): por ejemplo, queaddStockseguido deremoveStockcon la misma cantidad deja el stock como estaba. Y al menos un caso propio de entrada y salida entests/, con un nombre distinto de los públicos (por ejemplo,MakeChange.mio-1.iny.expected). - Un
README.mdcorto: su nombre, qué funciona y qué no, y una respuesta de dos o tres líneas: ¿por qué el precio va en céntimos en unlongy no en soles en undouble? Muestren un ejemplo enjshelldondedoubledé un resultado equivocado.