Введение
Друзья готовят пикник. В двух рецептах есть общие ингредиенты, некоторые продукты уже дома, а магазин продаёт целые упаковки. Нужно закончить повторно используемую программу Python, превращающую эти данные в полезный список покупок.
Сначала вы запустите небольшой рабочий пример, затем улучшите подготовленный модуль вычислений и создадите полный CSV. Код picnic.py для параметров командной строки и записи CSV, а также синтетические рецепты предоставлены. Предполагается знание списков, словарей, циклов, функций, основных исключений Python и базового ввода в терминале. Новые форматы и параметры объясняются здесь. Длительность включает чтение и обычные ошибки; это оценка автора, а не измерение работы начинающих.
Читаем небольшой список для пикника
На этом шаге вы создадите список для салата на троих и свяжете строки CSV с JSON. Начните в WebIDE: откройте Terminal, выберите New Terminal. Все команды этой работы вводятся там. Подготовленные файлы не зависят от предыдущих работ.
Нужны списки, словари, циклы, функции и основные исключения Python. JSON, запись CSV и параметры объясняются ниже. Количества целые: граммы (g), миллилитры (ml) или штуки (each), одна единица на ID ингредиента. Пересчёт единиц и питание не рассматриваются.
Начинаем с рабочего малого примера
cd меняет текущий каталог. Абсолютный путь начинается с /; последующий относительный путь, например data/menu.json, разрешается от текущего каталога. Если после перехода путь не найден, вернитесь сюда той же командой cd.
cd /home/labex/project/picnic
cat выводит текстовый файл. Прочитайте малое меню:
cat data/sample-menu.json
JSON использует {} для объектов, загружаемых как словари Python, [] для списков и двойные кавычки для строковых ключей. У рецепта есть объект ingredients: "chickpea": 70 означает 70 г на человека, не 70 упаковок. chickpea — устойчивый ID, а name — отображаемое название. Одинаково названные продукты не обязательно взаимозаменяемы.
cat data/products.json
Таблица продуктов содержит name, unit и положительное целое pack_size. Нут продаётся по 200 г. Малый пример использует data/empty-pantry.json: пустой объект {} означает отсутствие запасов. Остальные исходные количества — неотрицательные целые. Отсутствующий ID в запасах также означает ноль; отсутствие продукта — ошибка, ведь размер упаковки неизвестен.
Предоставленный код picnic.py обрабатывает параметры и записывает CSV. Его запускает python3. Параметры с -- обозначают следующее значение: меню, запасы, продукты, положительное число людей и путь выходного CSV. --output указывает создаваемый или заменяемый файл, поэтому используйте показанные новые имена. Исходные JSON не меняются.
python3 picnic.py --menu data/sample-menu.json --pantry data/empty-pantry.json --products data/products.json --people 3 --output sample.csv
Ожидаемое сообщение:
Created 2 ingredient rows in sample.csv
cat sample.csv
Заголовок называет столбцы. Для нута нужно 70 * 3 = 210 г. Две упаковки по 200 г дают 400 г, остаётся 190 г. Томатов нужно 120 г: упаковка 250 г оставляет 130 г. Этот пример работает, но общее меню и имеющиеся запасы выявят две ошибки начальной программы.
Итоговый CSV содержит одну строку на ID нужного ингредиента и столбцы ingredient_id,name,unit,required,stock,shortfall,packs,leftover. stock — первоначальный запас, не только использованная часть. leftover включает неиспользованный исходный запас. Порядок строк и столбцов свободный, но поля и связи должны быть точными. Сохраните picnic.py, повторно используемую вычислительную программу и все оригиналы data/. Допустимы эквивалентные вычисления Python при сохранении интерфейса и форматов.
Объединяем общие ингредиенты по ID
На этом шаге вы исправите расчёт общего спроса до решения о покупках. Продолжайте в том же терминале WebIDE.
cat data/menu.json
Салат требует 70 г нута на человека, роллы — 40 г. На пятерых нужно (70 + 40) * 5 = 550 г. Покупки отдельно по рецептам могут дать лишние упаковки. Сначала объедините все потребности, затем вычтите запас один раз и один раз округлите оставшуюся нехватку.
cat calculations.py
В total_requirements начальный код каждый раз присваивает ID значение amount * people. Второй рецепт с тем же ID заменяет количество первого. А totals.get(ingredient_id, 0) читает накопленную сумму, либо ноль для нового ID. Прибавьте количество следующего рецепта, умноженное на число людей.
Заменяем небольшой модуль вычислений
Следующая команда записывает модуль целиком, чтобы сохранить отступы и границы функций. cat > calculations.py перенаправляет текст в файл, заменяя содержимое. <<'PYCODE' начинает here-document: вставьте всё до последней строки PYCODE. Кавычки вокруг разделителя запрещают оболочке интерпретировать текст Python. Заменяется лишь calculations.py, JSON и CSV не затрагиваются. При опечатке повторите весь блок. Отступы Python важны: сохраняйте блоки по четыре пробела.
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
Пока исправлено лишь суммирование. Создайте основной список на пятерых:
python3 picnic.py --menu data/menu.json --pantry data/pantry.json --products data/products.json --people 5 --output five.csv
cat five.csv
Поле required для нута теперь должно быть 550, для томатов — 200, для роллов — 5. stock ещё равен нулю: вторая функция игнорирует запасы. Этот промежуточный список пока нельзя использовать для покупок.
Покупаем целые упаковки после учёта запасов
На этом шаге вы закончите вычисления и прочитаете список с учётом домашних запасов. Продолжайте в терминале WebIDE.
cat data/pantry.json
На пятерых нужно 550 г нута, дома есть 150 г: не хватает 400 г. Две упаковки по 200 г покрывают это точно. Томатов нужно 200 г, есть 250 г: нехватка и покупка равны нулю, остаётся 50 г. Роллов нужно 5, есть 2, не хватает 3: покупка упаковки из 6 оставляет 3 штуки.
Используйте max(0, required - stock), чтобы избыток не давал отрицательную покупку. Для неотрицательной целой нехватки s и положительного размера p выражение (s + p - 1) // p округляет число упаковок вверх; // — целочисленное деление с округлением вниз. При упаковках 200 г нехватка 0 даёт 0 упаковок, 200 — 1, 201 — 2. Точные границы соблюдаются без плавающей точки.
Конечный остаток равен stock + packs * pack_size - required. Только packs * pack_size - shortfall потеряло бы неиспользованный запас, когда он превышает потребность.
Замените модуль двумя готовыми функциями. Here-document работает как на предыдущем шаге.
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
Ожидаемое содержимое; эквивалентное решение может изменить порядок:
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
Список пригоден для покупок: две упаковки нута, никаких томатов и одна упаковка роллов. Оставляйте строки без покупок, чтобы отличать «уже обеспечено» от пропущенного ингредиента. Строки нужны только ID этого меню, не посторонним запасам.
Повторно используем программу и исправляем ввод
На этом шаге вы разберёте предоставленный код записи файлов в picnic.py, примените вычисления к другому меню и исправите обычные ошибки. Продолжайте в терминале WebIDE. Переменные или предыдущие сеансы оболочки не нужны: каждая команда явно передаёт пути.
cat picnic.py
argparse читает именованные параметры и предоставляет --help; type=int отклоняет нецелое число людей, следующая проверка — ноль и отрицательные числа. load_json использует open(..., encoding="utf-8") и json.load для чтения полных структурированных объектов. Блок with закрывает файл при завершении. Модуль вычислений возвращает список словарей строк.
Все входы и вычисления обрабатываются до открытия выхода. Проверка сравнивает необходимые ID с ID продуктов и вызывает ValueError, если отсутствуют сведения об упаковке. csv.DictWriter записывает именованные столбцы, заголовок и строки. newline="" передаёт CSV обработку окончаний строк, UTF-8 сохраняет названия. Не заменяйте запись CSV соединением строк запятыми: будущее название с запятой должно остаться одним полем.
Код picnic.py перехватывает ошибки файлов, JSON/значений и отсутствующих ключей, пишет объяснение в стандартный поток ошибок и завершается ненулевым кодом. Защита от прерванной записи на диск не обещается, но неправильное число людей или отсутствующий продукт должны сохранять прежний выход.
Читаем ожидаемый отказ и восстанавливаемся
Две следующие команды намеренно завершаются ошибкой. Это ожидается, терминал остаётся доступен. Первая просит ноль людей и должна вывести people must be a positive integer до замены five.csv.
python3 picnic.py --menu data/menu.json --pantry data/pantry.json --products data/products.json --people 0 --output five.csv
Во второй неверно написано имя меню. Ожидаются Cannot make list: и сообщение об отсутствующем файле, а не успешный пустой список. Посмотрите имена в data/ и повторите с правильным путём.
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 намеренно неполон: в нём отсутствуют необходимые товары. Следующая команда должна вывести missing product: и сохранить five.csv. Неизвестная упаковка не означает нулевой запас или нулевую покупку.
python3 picnic.py --menu data/menu.json --pantry data/pantry.json --products data/missing-products.json --people 5 --output five.csv
cat five.csv
Хороший список на пятерых должен остаться как на прошлом шаге. Для восстановления выберите полную таблицу продуктов, не меняя оригиналы.
Создаём другой список
cat data/other-menu.json
В этом меню только томаты и роллы. На четверых нужно 200 г томатов и 8 роллов. Та же программа должна использовать исходный запас без переноса ингредиентов прошлого запуска.
python3 picnic.py --menu data/other-menu.json --pantry data/pantry.json --products data/products.json --people 4 --output other.csv
cat other.csv
Томаты не покупаются, остаётся 50 г. Роллов не хватает 6: ровно одна упаковка без остатка. Строки нута быть не должно. five.csv, sample.csv, программа и исходные JSON сохраняются. Другое меню и новое имя выхода позволяют оставить оба плана. Проверки читают ваши реальные файлы и запускают неизменённую программу на временных входах; они не создают и не исправляют рабочие списки.
Резюме
Вы объединили порционные количества по ID, один раз вычли запас и округлили до упаковок только оставшуюся нехватку. Вы создали настоящие CSV, сохранили значимые строки без покупок, применили программу к другому меню и исправили неправильный ввод, не заменяя хороший результат.



