Introdução
Amigos estão preparando um piquenique. Duas receitas compartilham ingredientes, já há alguns suprimentos em casa e a loja vende embalagens inteiras. Sua tarefa é terminar um programa Python reutilizável que transforme essas informações em uma lista de compras útil.
Primeiro você executará um pequeno caso funcional; depois melhorará o módulo de cálculo e criará um CSV completo. O código de picnic.py para opções e escrita CSV e as receitas sintéticas são fornecidos. Este laboratório pressupõe listas, dicionários, laços, funções, exceções básicas de Python e entrada básica no terminal. Os novos formatos e opções são explicados aqui. A duração inclui leitura e erros comuns; é uma estimativa do autor, não um tempo medido com iniciantes.
Ler uma pequena lista de piquenique
Nesta etapa, você gerará uma lista para uma salada para três pessoas e relacionará cada linha CSV à entrada JSON. Comece no WebIDE, abra o menu Terminal e escolha New Terminal. Todos os comandos deste laboratório são executados nesse terminal. Os arquivos preparados são independentes de laboratórios anteriores.
Você precisa conhecer listas, dicionários, laços, funções e exceções básicas de Python. JSON, escrita CSV e opções usadas são explicados abaixo. As quantidades são inteiras: gramas (g), mililitros (ml) ou unidades (each), com uma unidade por ID de ingrediente. Não há conversão de unidades nem planejamento nutricional.
Começar com um pequeno caso funcional
cd altera o diretório atual. Um caminho absoluto começa com /; um caminho relativo posterior, como data/menu.json, é resolvido a partir do diretório atual. Se um caminho falhar depois de você mudar de lugar, volte aqui com o mesmo cd.
cd /home/labex/project/picnic
cat imprime um arquivo de texto. Leia o menu pequeno:
cat data/sample-menu.json
JSON usa {} para objetos, carregados como dicionários Python, [] para listas e aspas duplas para chaves de texto. Cada receita tem um objeto ingredients: "chickpea": 70 significa 70 g por pessoa, não 70 embalagens. chickpea é um ID estável, enquanto name é o nome exibido do produto. Dois produtos podem ter o mesmo nome sem serem intercambiáveis.
cat data/products.json
A tabela de produtos fornece name, unit e um inteiro positivo pack_size. O grão-de-bico vem em embalagens de 200 g. O caso pequeno usa data/empty-pantry.json, cujo objeto vazio {} significa nenhum estoque. As demais quantidades fornecidas são inteiros não negativos. Um ID ausente na despensa também significa estoque zero; produto ausente é erro, pois o tamanho da embalagem é desconhecido.
O código fornecido em picnic.py trata opções e escrita CSV. python3 o executa. Opções iniciadas por -- identificam o próximo valor: menu, despensa, tabela de produtos, número positivo de pessoas e caminho de saída CSV. --output indica um arquivo a criar ou substituir; use os novos nomes mostrados. Os JSON originais não mudam.
python3 picnic.py --menu data/sample-menu.json --pantry data/empty-pantry.json --products data/products.json --people 3 --output sample.csv
Mensagem esperada:
Created 2 ingredient rows in sample.csv
cat sample.csv
O cabeçalho identifica as colunas. O grão-de-bico exige 70 * 3 = 210 g. Duas embalagens de 200 g fornecem 400 g e sobram 190 g. Os tomates precisam de 120 g; uma embalagem de 250 g deixa 130 g. Esse caso funciona, mas um menu compartilhado e estoques existentes revelarão dois erros do programa inicial.
O CSV final tem uma linha por ID de ingrediente necessário, com as colunas ingredient_id,name,unit,required,stock,shortfall,packs,leftover. stock é o estoque original, não apenas a parte utilizada. leftover inclui estoque original não consumido. A ordem de linhas e colunas é livre, mas todos os campos e relações devem estar corretos. Preserve picnic.py, um programa de cálculo reutilizável e todos os originais de data/. Cálculos Python equivalentes são permitidos; a interface e os formatos permanecem iguais.
Combinar ingredientes compartilhados por ID
Nesta etapa, você corrigirá as necessidades compartilhadas antes de decidir as compras. Continue no mesmo terminal do WebIDE.
cat data/menu.json
A salada usa 70 g de grão-de-bico por pessoa e os wraps usam 40 g. Para cinco pessoas, o total deve ser (70 + 40) * 5 = 550 g. Comprar separadamente para cada receita pode gerar embalagens extras. Primeiro combine todas as necessidades; depois desconte o estoque uma vez e arredonde a falta restante uma única vez.
cat calculations.py
Em total_requirements, o programa inicial atribui amount * people ao ID a cada ocorrência. A segunda receita com o mesmo ID substitui a necessidade da primeira. Já totals.get(ingredient_id, 0) lê o total acumulado, ou zero para um ID novo. Some a ele a quantidade da próxima receita multiplicada pelo número de pessoas.
Substituir o pequeno módulo de cálculo
O comando seguinte escreve o módulo completo para manter claras a indentação e as funções. cat > calculations.py redireciona o texto para o arquivo, substituindo seu conteúdo. <<'PYCODE' inicia um here-document: cole tudo até a linha final PYCODE. As aspas no delimitador impedem o shell de interpretar o texto Python. Apenas calculations.py é substituído; JSON e CSV permanecem intactos. Se houver um erro de digitação, execute novamente o bloco inteiro. A indentação Python importa: preserve blocos de quatro espaços.
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 enquanto, apenas a soma foi corrigida. Gere a lista principal para cinco pessoas:
python3 picnic.py --menu data/menu.json --pantry data/pantry.json --products data/products.json --people 5 --output five.csv
cat five.csv
O campo required do grão-de-bico deve agora ser 550; tomates precisam de 200 e wraps de 5. stock ainda é zero porque a segunda função ignora a despensa. Esta lista intermediária ainda não serve para fazer compras.
Comprar embalagens inteiras depois de usar o estoque
Nesta etapa, você terminará o cálculo e lerá uma lista que considera o que já existe em casa. Continue no terminal do WebIDE.
cat data/pantry.json
Para cinco pessoas, são necessários 550 g de grão-de-bico e há 150 g: faltam 400 g, exatamente duas embalagens de 200 g. Tomates exigem 200 g, mas há 250 g: a falta e a compra são zero e sobram 50 g. São necessários 5 wraps e há 2; a falta de 3 exige uma embalagem de 6, deixando 3 ao final.
Use max(0, required - stock) para que estoque excedente não gere uma compra negativa. Para falta inteira não negativa s e tamanho positivo p, (s + p - 1) // p arredonda para cima o número de embalagens; // é divisão inteira com arredondamento para baixo. Com embalagens de 200 g, falta 0 resulta em 0 embalagens, 200 em 1 e 201 em 2. Assim os limites exatos funcionam sem ponto flutuante.
A sobra final é stock + packs * pack_size - required. Calcular apenas packs * pack_size - shortfall ignoraria o estoque não utilizado quando ele supera a necessidade.
Substitua o módulo pelas duas funções completas. O here-document funciona como na etapa 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
Conteúdo esperado; uma implementação equivalente pode alterar a ordem:
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
A lista é utilizável: compre duas embalagens de grão-de-bico, nenhum tomate e uma embalagem de wraps. Mantenha linhas de compra zero para distinguir “já coberto” de um ingrediente omitido. Só os ID necessários neste menu precisam de linhas; estoques sem relação com ele não precisam.
Reutilizar o programa e recuperar-se de erros de entrada
Nesta etapa, você compreenderá o código de escrita fornecido em picnic.py, reutilizará os cálculos com outro menu e corrigirá erros comuns. Continue no terminal do WebIDE. Variáveis e sessões anteriores do shell não são necessárias: cada comando passa explicitamente seus caminhos de entrada.
cat picnic.py
argparse lê opções nomeadas e fornece --help; type=int rejeita números de pessoas não inteiros e a verificação seguinte rejeita zero e negativos. load_json usa open(..., encoding="utf-8") e json.load para ler objetos estruturados completos. Um bloco with fecha o arquivo ao terminar. O módulo de cálculo retorna uma lista de dicionários de linha.
Todas as entradas e cálculos são processados antes de abrir a saída. A verificação de produtos compara os ID necessários com os ID de produtos e lança ValueError se faltarem informações de embalagem. csv.DictWriter escreve colunas nomeadas, cabeçalho e linhas. newline="" deixa o módulo CSV tratar os finais de linha; UTF-8 preserva nomes. Não substitua a escrita CSV pela junção de textos com vírgulas: um futuro nome com vírgula deve continuar sendo um único campo.
O código fornecido em picnic.py captura erros de arquivo, JSON/valor e chave ausente, explica o problema no erro padrão e encerra com status diferente de zero. Ele não promete proteção contra escrita em disco interrompida, mas um número de pessoas inválido ou produtos ausentes devem deixar uma saída existente intacta.
Ler uma falha esperada e recuperar-se
Os próximos dois comandos falham intencionalmente. O erro é esperado e o terminal continua disponível. O primeiro pede zero pessoas e deve mostrar people must be a positive integer antes de substituir five.csv.
python3 picnic.py --menu data/menu.json --pantry data/pantry.json --products data/products.json --people 0 --output five.csv
O segundo escreve incorretamente o nome do menu. Espere Cannot make list: com uma mensagem de arquivo ausente, não uma lista vazia considerada bem-sucedida. Confira os nomes em data/ e repita com o caminho correto.
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 é uma tabela intencionalmente incompleta: faltam produtos exigidos pelo menu. O próximo comando deve mostrar missing product: e preservar five.csv. Embalagem desconhecida não equivale a estoque ou compra zero.
python3 picnic.py --menu data/menu.json --pantry data/pantry.json --products data/missing-products.json --people 5 --output five.csv
cat five.csv
A lista correta para cinco pessoas deve continuar igual à etapa anterior. Recupere-se escolhendo o arquivo completo de produtos, sem alterar os dados originais.
Fazer outra lista de compras
cat data/other-menu.json
Este menu tem apenas tomates e wraps. Para quatro pessoas, exige 200 g de tomates e 8 wraps. O mesmo programa deve usar a despensa original sem carregar ingredientes da execução 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
Não se compram tomates e sobram 50 g. Faltam 6 wraps, exatamente uma embalagem e nenhuma sobra. Não deve aparecer uma linha de grão-de-bico. five.csv, sample.csv, o programa e os JSON originais continuam disponíveis. Outro menu e um novo nome de saída permitem guardar os dois planos. As verificações leem suas saídas reais e executam o programa sem alterações em casos temporários; não criam nem corrigem as listas do seu ambiente.
Resumo
Você combinou quantidades por pessoa por ID, descontou o estoque uma única vez e arredondou somente a falta para embalagens inteiras. Produziu listas CSV reais, manteve linhas significativas de compra zero, reutilizou o programa para outro menu e corrigiu entradas inválidas sem substituir um resultado correto.



