Crear una lista reutilizable para un pícnic

PythonBeginner
Practicar Ahora

Introducción

Unos amigos preparan un pícnic. Dos recetas comparten ingredientes, ya hay algunas provisiones en casa y la tienda vende paquetes completos. Tu tarea consiste en terminar un programa Python reutilizable que convierta esos datos en una lista de compras útil.

Primero ejecutarás un pequeño caso que funciona; después mejorarás el módulo de cálculo y crearás un CSV completo. Se proporcionan el código de picnic.py para las opciones y la escritura CSV, y recetas sintéticas. Se presuponen listas, diccionarios, bucles, funciones, excepciones básicas de Python y entrada básica en terminal. Aquí se explican los formatos y opciones nuevos. El tiempo estimado incluye lectura y errores habituales; es una estimación del autor, no una duración medida con principiantes.

Leer una pequeña lista para el pícnic

En este paso generarás una lista para tres personas con una ensalada y relacionarás cada fila CSV con su entrada JSON. Empieza en WebIDE. Abre su menú Terminal y selecciona New Terminal. Todos los comandos del laboratorio se introducen ahí. Los archivos preparados son independientes de laboratorios anteriores.

Necesitas listas, diccionarios, bucles, funciones y excepciones básicas de Python. A continuación se explican JSON, la escritura CSV y las opciones utilizadas. Se trabaja con cantidades enteras: gramos (g), mililitros (ml) o unidades (each), con una unidad por ID de ingrediente. No se convierten unidades ni se planifica la nutrición.

Empezar con un caso pequeño que funciona

cd cambia el directorio actual. Una ruta absoluta empieza por /; las rutas relativas posteriores, como data/menu.json, se resuelven desde el directorio actual. Si una ruta falla después de moverte, vuelve aquí con el mismo cd.

cd /home/labex/project/picnic

cat imprime un archivo de texto. Lee el menú pequeño:

cat data/sample-menu.json

JSON utiliza {} para objetos (cargados como diccionarios Python), [] para listas y comillas dobles para claves de cadena. Cada receta tiene un objeto ingredients: "chickpea": 70 significa 70 g por persona, no 70 paquetes. chickpea es un ID estable; name es la etiqueta visible del producto. Dos productos pueden compartir nombre sin ser intercambiables.

cat data/products.json

La tabla de productos proporciona name, unit y un entero positivo pack_size. Los garbanzos vienen en paquetes de 200 g. El caso pequeño usa data/empty-pantry.json, cuyo objeto vacío {} indica que no hay existencias. Las demás cantidades suministradas son enteros no negativos. Un ID ausente de la despensa también significa cero existencias; un producto ausente es un error, porque se desconoce su tamaño de paquete.

El código proporcionado en picnic.py gestiona las opciones y la escritura CSV. python3 lo ejecuta. Las opciones que empiezan por -- indican el significado del valor siguiente: menú, despensa, productos, número positivo de personas y ruta CSV de salida. --output nombra un archivo que se creará o reemplazará; usa los nombres nuevos indicados. Los JSON originales permanecen intactos.

python3 picnic.py --menu data/sample-menu.json --pantry data/empty-pantry.json --products data/products.json --people 3 --output sample.csv

Mensaje esperado:

Created 2 ingredient rows in sample.csv
cat sample.csv

La cabecera identifica las columnas. Se necesitan 70 * 3 = 210 g de garbanzos. Dos paquetes de 200 g aportan 400 g y sobran 190 g. Los tomates requieren 120 g; un paquete de 250 g deja 130 g. Este caso funciona, pero un menú compartido y las existencias revelarán dos errores del programa inicial.

El CSV final contiene una fila por ID de ingrediente requerido, con estas columnas: ingredient_id,name,unit,required,stock,shortfall,packs,leftover. stock es la cantidad original disponible, no solo la utilizada. leftover incluye las existencias originales no consumidas. El orden de filas y columnas no importa, pero todos los campos y relaciones deben ser correctos. Conserva picnic.py, un programa de cálculo reutilizable y todos los originales de data/. Se permiten cálculos Python equivalentes; la interfaz y los formatos no cambian.

Combinar ingredientes compartidos por ID

En este paso corregirás el cálculo compartido antes de decidir las compras. Continúa en el mismo terminal de WebIDE.

cat data/menu.json

La ensalada utiliza 70 g de garbanzos por persona y los wraps, 40 g. Para cinco personas se necesitan (70 + 40) * 5 = 550 g. Comprar por separado para cada receta puede añadir paquetes innecesarios. Primero combina toda la demanda; después descuenta las existencias una sola vez y redondea una sola vez el faltante.

cat calculations.py

En total_requirements, el programa inicial asigna amount * people al ID cada vez. La segunda receta que usa ese ID reemplaza la primera cantidad. En cambio, totals.get(ingredient_id, 0) obtiene el total acumulado, o cero si el ID es nuevo. Súmale la cantidad de la siguiente receta multiplicada por las personas.

Reemplazar el pequeño módulo de cálculo

El siguiente comando escribe el módulo completo para mantener claras la sangría y los límites de las funciones. cat > calculations.py redirige el texto a ese archivo y reemplaza su contenido. <<'PYCODE' inicia un documento integrado: pega todo hasta la línea final PYCODE. Las comillas del delimitador impiden que el shell interprete el texto Python. Solo se reemplaza calculations.py; los JSON y CSV no cambian. Si cometes una errata, vuelve a ejecutar el bloque completo. La sangría importa en Python: conserva bloques de cuatro espacios.

cat > calculations.py <<'PYCODE'
"""The two calculation functions you will improve."""


def total_requirements(menu, people):
    totals = {}
    for recipe in menu["recipes"]:
        for ingredient_id, amount in recipe["ingredients"].items():
            totals[ingredient_id] = totals.get(ingredient_id, 0) + amount * people
    return totals


def shopping_rows(totals, pantry, products):
    rows = []
    for ingredient_id, required in totals.items():
        product = products[ingredient_id]
        size = product["pack_size"]
        packs = (required + size - 1) // size
        rows.append({"ingredient_id": ingredient_id,
                     "name": product["name"], "unit": product["unit"],
                     "required": required, "stock": 0,
                     "shortfall": required, "packs": packs,
                     "leftover": packs * size - required})
    return rows
PYCODE

Por ahora solo está corregida la suma. Genera la lista principal para cinco personas:

python3 picnic.py --menu data/menu.json --pantry data/pantry.json --products data/products.json --people 5 --output five.csv
cat five.csv

El campo required de los garbanzos debe ser 550; los tomates requieren 200 y los wraps, 5. stock sigue a cero porque la segunda función ignora la despensa. Esta lista intermedia todavía no está lista para ir de compras.

Comprar paquetes completos después de usar existencias

En este paso terminarás el cálculo y leerás una lista que contempla lo que ya hay en casa. Continúa en el terminal de WebIDE.

cat data/pantry.json

Para cinco personas hacen falta 550 g de garbanzos y hay 150 g disponibles: faltan 400 g. Dos paquetes de 200 g cubren exactamente esa cantidad. Se necesitan 200 g de tomates, pero hay 250 g: el faltante y las compras son cero y sobran 50 g. Se necesitan 5 wraps, hay 2 y faltan 3; un paquete de 6 deja un resto final de 3.

Usa max(0, required - stock) para que un exceso de existencias no produzca compras negativas. Para un faltante entero no negativo s y un tamaño positivo p, (s + p - 1) // p redondea hacia arriba a paquetes completos; // es la división entera hacia abajo. Con paquetes de 200 g, un faltante de 0 requiere 0 paquetes, 200 requiere 1 y 201 requiere 2. Así se respetan los límites exactos sin aritmética de coma flotante.

El resto final es stock + packs * pack_size - required. Calcular solo packs * pack_size - shortfall omitiría las existencias no consumidas cuando el stock supera la necesidad.

Reemplaza el módulo con ambas funciones completas. El documento integrado funciona igual que en el paso anterior.

cat > calculations.py <<'PYCODE'
"""Calculate requirements before deciding what to buy."""


def total_requirements(menu, people):
    totals = {}
    for recipe in menu["recipes"]:
        for ingredient_id, amount in recipe["ingredients"].items():
            totals[ingredient_id] = totals.get(ingredient_id, 0) + amount * people
    return totals


def shopping_rows(totals, pantry, products):
    rows = []
    for ingredient_id, required in totals.items():
        product = products[ingredient_id]
        stock = pantry.get(ingredient_id, 0)
        shortfall = max(0, required - stock)
        size = product["pack_size"]
        packs = (shortfall + size - 1) // size
        rows.append({"ingredient_id": ingredient_id,
                     "name": product["name"], "unit": product["unit"],
                     "required": required, "stock": stock,
                     "shortfall": shortfall, "packs": packs,
                     "leftover": stock + packs * size - required})
    return rows
PYCODE
python3 picnic.py --menu data/menu.json --pantry data/pantry.json --products data/products.json --people 5 --output five.csv
cat five.csv

Contenido esperado; una implementación equivalente puede cambiar el orden:

ingredient_id,name,unit,required,stock,shortfall,packs,leftover
chickpea,Chickpeas,g,550,150,400,2,0
tomato,Tomatoes,g,200,250,0,0,50
wrap,Wraps,each,5,2,3,1,3

La lista ya es útil: compra dos paquetes de garbanzos, ningún tomate y un paquete de wraps. Conserva las filas de cero compras para distinguir «ya cubierto» de un ingrediente omitido por error. Solo necesitan filas los ID requeridos por este menú; no hacen falta los artículos de despensa ajenos a él.

Reutilizar el programa y recuperarse de errores de entrada

En este paso comprenderás el código de escritura proporcionado en picnic.py, reutilizarás los cálculos con otro menú y corregirás errores habituales. Continúa en el terminal de WebIDE. No se requieren variables ni sesiones anteriores: cada comando pasa explícitamente sus rutas de entrada.

cat picnic.py

argparse lee las opciones y ofrece --help; type=int rechaza números de personas no enteros y la comprobación siguiente rechaza cero o negativos. load_json utiliza open(..., encoding="utf-8") y json.load para leer objetos estructurados completos. Un bloque with cierra el archivo al terminar. El módulo de cálculo devuelve una lista de diccionarios de fila.

Se procesan todas las entradas y cálculos antes de abrir la salida. La comprobación de productos compara los ID requeridos con los ID de productos y lanza ValueError si falta información de embalaje. csv.DictWriter escribe columnas nombradas, cabecera y filas. newline="" permite que CSV gestione los saltos de línea; UTF-8 conserva los nombres. No sustituyas la escritura CSV por unir cadenas con comas: un futuro nombre que contenga una coma debe seguir siendo un solo campo.

El código proporcionado en picnic.py captura errores de archivo, JSON/valor y clave ausente, muestra una explicación en el error estándar y termina con estado distinto de cero. No promete proteger una escritura interrumpida en disco; sí debe conservar una salida existente ante un número de personas no válido o productos ausentes.

Leer un fallo esperado y recuperarse

Los dos comandos siguientes fallan intencionadamente. El error es esperado y el terminal sigue disponible. El primero pide cero personas y debe mostrar people must be a positive integer antes de reemplazar five.csv.

python3 picnic.py --menu data/menu.json --pantry data/pantry.json --products data/products.json --people 0 --output five.csv

El segundo escribe mal el nombre del menú. Espera Cannot make list: y un mensaje de archivo ausente, no una lista vacía considerada correcta. Consulta los nombres de data/ y repite con la ruta correcta.

python3 picnic.py --menu data/menus.json --pantry data/pantry.json --products data/products.json --people 5 --output five.csv
ls data

missing-products.json es una tabla incompleta intencional: faltan productos necesarios. El siguiente comando debe mostrar missing product: y conservar five.csv. Desconocer el embalaje no equivale a cero existencias ni a cero compras.

python3 picnic.py --menu data/menu.json --pantry data/pantry.json --products data/missing-products.json --people 5 --output five.csv
cat five.csv

La lista correcta para cinco personas debe seguir coincidiendo con el paso anterior. Para recuperarte, selecciona el archivo completo de productos sin modificar los originales.

Crear otra lista de compras

cat data/other-menu.json

Este menú solo contiene tomates y wraps. Para cuatro personas necesita 200 g de tomates y 8 wraps. El mismo programa debe usar la despensa original sin arrastrar ingredientes de la ejecución anterior.

python3 picnic.py --menu data/other-menu.json --pantry data/pantry.json --products data/products.json --people 4 --output other.csv
cat other.csv

No se compran tomates y sobran 50 g. Faltan 6 wraps: exactamente un paquete y ningún sobrante. No debe haber fila de garbanzos. Se conservan five.csv, sample.csv, el programa y los JSON originales. Otro menú y otro nombre de salida permiten guardar ambos planes. Las comprobaciones leen tus salidas reales y ejecutan el programa sin modificarlo sobre casos temporales; no crean ni reparan las listas de tu entorno.

Resumen

Has combinado cantidades por persona por ID, descontado existencias una sola vez y redondeado solo el faltante a paquetes completos. Has creado listas CSV reales, mantenido filas significativas de cero compras, reutilizado el programa con otro menú y corregido entradas no válidas sin reemplazar un resultado correcto.