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 con IO.readln y escribiendo con IO.println.
  • Manejar dinero sin errores de redondeo: céntimos en un long, nunca double.
  • 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 Makefile del proyecto y leer la salida de las pruebas.

Lo que necesitan de las semanas 1 y 2

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.XX con lo que falta, y termina.
  • Si alcanza, escribe Vuelto: S/ X.XX y 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.XX o N moneda de S/ X.XX, con billetes y monedas en plural cuando N es 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), donde N es la suma de las cantidades; si N es 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 llaman code() y stock(), no getCode(): 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 con stockValueCents(): el producto de un long por un int es long, pero el de dos int puede 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:

  1. Una línea por producto, en el orden de la entrada: código nombre: S u. x S/ precio = S/ valor, donde S es el stock y el valor sale de stockValueCents().
  2. Valor del stock: S/ X.XX, la suma de los valores.
  3. Más valioso: código nombre, el producto con mayor valor de stock; si hay empate, el primero de la entrada.
  4. Por reponer (5 u. o menos): A, B, C con los códigos de los productos con stock de 5 o menos, en el orden de la entrada y separados por coma y espacio, o Por 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:

  1. Para cada caso de tests/, ejecuta el programa con esa entrada y compara su salida con la esperada usando diff. Escribe ok o FALLA por caso; si falla, diff -u muestra las líneas distintas: las que empiezan con - son las esperadas y las que empiezan con +, las que escribió su programa.
  2. 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:

  1. Los cuatro archivos .java completos y la carpeta tests/ sin cambios.
  2. Al menos una prueba JUnit propia en ProductTest.java (o varias): por ejemplo, que addStock seguido de removeStock con la misma cantidad deja el stock como estaba. Y al menos un caso propio de entrada y salida en tests/, con un nombre distinto de los públicos (por ejemplo, MakeChange.mio-1.in y .expected).
  3. Un README.md corto: 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 un long y no en soles en un double? Muestren un ejemplo en jshell donde double dé un resultado equivocado.