مسئلهٔ واقعی گزارش هزینه چیست؟
وقتی هر پرداخت یک رکورد مستقل است، نگاهکردن به فهرست خام تصویر روشنی از مصرف نمیدهد. هدف این آموزش، تبدیل چند رکورد به دو خروجی مشخص است: جمع هر دسته و جمع کل. این مسئله را با دادهٔ کوچک و کاملاً ساختگی حل میکنیم؛ اتصال بانکی، خواندن اطلاعات حساب و ذخیرهٔ دادهٔ شخصی بخشی از تمرین نیست. پیشنیاز، شناخت حلقه، فهرست و دیکشنری است.
ابتدا واحد پول را مشخص کنید
عدد ۱۲۰ بدون واحد معنای مالی کامل ندارد. در این پروژه همهٔ مبلغها صحیح، غیرمنفی و در یک واحد ثابتاند. تبدیل ارز، تخفیف و بازپرداخت را وارد همین قرارداد نکنید؛ هرکدام رفتار مستقلی میخواهد. در برنامهٔ واقعی معمولاً مقدار را در کوچکترین واحد موردنیاز نگه میداریم. مخلوطکردن ریال و تومان یا عدد اعشاری و صحیح، خطایی است که حلقهٔ درست هم آن را اصلاح نمیکند.
دیکشنری چگونه انباشته میشود؟
برای هر رکورد، نام دسته را کلید قرار دهید. مقدار قبلی را با get بخوانید و اگر دسته هنوز دیده نشده، از صفر شروع کنید. سپس مبلغ همان رکورد را اضافه کنید. این روش نام دستههای تکراری را یکپارچه میکند، اما دو نام متفاوت را حدس نمیزند. «خوراک» و «مواد غذایی» تا وقتی نگاشت صریحی تعریف نشده دو دستهاند؛ اصلاح معنایی را با جمعزدن اشتباه نگیرید.
انتخاب بین dict و defaultdict
defaultdict(int) مقدار آغازین صفر را هنگام دسترسی با کروشه میسازد و برای شمارش و جمعزدن مناسب است. بااینحال get کارخانهٔ مقدار پیشفرض را فراخوانی نمیکند. برای تمرین مقدماتی، dict و get وابستگی ذهنی کمتری دارند و ساختار را آشکار میکنند. برای خروجی JSON نیز بهتر است نگاشت معمولی بسازید. انتخاب ابزار باید به خوانایی کمک کند، نه اینکه مسئلهٔ کوتاه را پیچیدهتر کند.
آزمون ناورداهای گزارش
علاوه بر یک نمونهٔ عددی، رابطهٔ جمع کل و مجموع دستهها را بررسی کنید. افزودن رکورد با مبلغ صفر نباید جمع را تغییر دهد. جابهجایی ترتیب رکوردها نباید مقدارهای نهایی را عوض کند. دو پرداخت یکسان لزوماً دادهٔ تکراری اشتباه نیستند؛ حذف خودکار آنها ممکن است هزینهٔ واقعی را از گزارش پاک کند. تست باید قرارداد کسبوکار را بسنجد، نه صرفاً نبودن خطای اجرا را.
مقیاسپذیری بدون پیچیدگی اضافی
این الگوریتم یک بار از رکوردها عبور میکند و حافظهٔ گزارش متناسب با تعداد دستهها رشد میکند. برای فهرست کوچک نیازی به نصب pandas یا ساخت پایگاه داده نیست. اگر ورودی فایل بزرگی شد، میتوان رکوردها را تدریجی خواند و همان انباشت را حفظ کرد. مشکل حافظه معمولاً از بارگذاری همهٔ ورودی میآید، نه از نگاشت کوچکی که فقط جمع هر دسته را نگه میدارد.
اشتباههای رایج و تمرین مستقل
قرار دادن totals برابر با مبلغ جدید بهجای افزودن آن، فقط آخرین پرداخت را حفظ میکند. مقداردهی دوبارهٔ دیکشنری داخل حلقه نیز گزارش قبلی را پاک میکند. برای خودآزمایی سه دسته و چند پرداخت صفر بسازید و جواب را دستی حساب کنید. سپس پروژهٔ مرتبط را با نمونهٔ تازه اجرا کنید؛ برابر بودن پاسخ یک مثال برای اثبات درستی همهٔ ورودیها کافی نیست.
نمونهکد و قرارداد پروژه
expenses: فهرست رکوردهای category (رشتهٔ غیرخالی) و amount (عدد صحیح غیرمنفی). دستهها عیناً حفظ میشوند؛ فهرست خالی مجاز است.
by_category: جمع هر دسته؛ total: مجموع همهٔ هزینهها؛ فهرست خالی نگاشت خالی و مجموع صفر دارد.
def solve(data):
totals = {}
for item in data['expenses']:
category = item['category']
totals[category] = totals.get(category, 0) + item['amount']
return {'by_category': totals, 'total': sum(totals.values())}
print(solve({'expenses': [{'category': 'خوراک', 'amount': 120}, {'category': 'رفت\u200cوآمد', 'amount': 80}, {'category': 'خوراک', 'amount': 50}]}))خروجی مورد انتظار
{
"by_category": {
"خوراک": 170,
"رفتوآمد": 80
},
"total": 250
}پرسشهای متداول
آیا مبلغ منفی هم پذیرفته میشود؟
نه، قرارداد فعلی فقط هزینهٔ غیرمنفی دارد. برای بازپرداخت باید معنای مبلغ منفی و اثر آن بر گزارش جداگانه تعریف شود.
آیا نام دستهها اصلاح میشوند؟
خیر. دسته در این تمرین عیناً حفظ میشود؛ همنامسازی به جدول نگاشت و تصمیم دامنه نیاز دارد.
تمرین عملی و قدم بعدی
پروژهٔ «خلاصهساز هزینههای ماهانه» همین قرارداد را در میزکار ارزیابی میکند. ابتدا خروجی نمونه را دستی محاسبه کنید، سپس تابع را اجرا کنید و یک ورودی مرزی متفاوت بسازید. نمونهکد راهی برای توضیح منطق است؛ برای یادگیری، علت هر شرط و انتخاب ساختار داده را بدون نگاهکردن به کد توضیح دهید. اجرای موفق با قبولی ارزیابی متفاوت است؛ نتیجه باید برای تمام حالتهای تعریفشده درست بماند.
منابع و مطالعهٔ بیشتر
برای بررسی جزئیات و ادامهٔ مطالعه؛ متن این راهنما مستقلاً برای نوونتیکس نوشته شده است.
