月間レポート機能

このレッスンで作るもの — ひと月分の記録から、合計・カテゴリ別・最高額・予算の残りをまとめて返す monthly_report を作ります。第 6 章の summarize と第 7 章の total_by_category を 1 つに束ねる回です。

月間レポート機能

前回の run_kakeibo で、追加と削除ができるようになりました。ただ、記録が溜まっただけでは何も分かりません。「今月は何にいくら使ったのか」を一目で分かる形にするのが今回の仕事です。

レポートに載せる 4 つの数字

家計簿を見返すときに知りたいのは、だいたい下記の 4 つです。

  1. total — 今月の合計金額
  2. by_category — カテゴリごとの合計を入れた辞書
  3. top — いちばん高かった支出の品名
  4. remaining — 予算からの残り

このうち total は第 6 章の summarize で、by_category は第 7 章の total_by_category ですでに作りました。remaining の考え方は第 1 章の remaining と第 3 章の check_budget で扱っています。新しいのは top だけです。

完成間近のアプリに機能を足すとき、ゼロから考えることはめったにありません。持っている部品を組み替えて、足りない 1 つだけを新しく書きます。

4 つを 1 回のループで求める

total のために 1 周、by_category のためにもう 1 周、と分けて書いても動きます。ですが記録は同じなので、1 周の中で全部やってしまうほうが素直です。

def monthly_report(records, budget): total = 0 by_category = {} top = "" top_price = -1 for record in records: price = record["price"] total = total + price category = record["category"] if category in by_category: by_category[category] = by_category[category] + price else: by_category[category] = price if price > top_price: top_price = price top = record["name"] return {"total": total, "by_category": by_category, "top": top, "remaining": budget - total}

by_category の 4 行は第 7 章そのままです。in でキーの有無を調べ、あれば足し、なければ新しく作ります。by_category[category] = by_category.get(category, 0) + price と 1 行で書いても構いません。

diagram (will load when visible)

最高額の探し方

top_price-1 から始めているのが大事なところです。金額は 0 円以上なので、1 件目は必ず price > top_price が成り立ち、top に品名が入ります。

ここを 0 から始めると、0 円の記録しかないときに 1 件も選ばれません。None から始める書き方もありますが、その場合は if top_price is None or price > top_price のように 2 段の判定が要ります。-1 にしておくと 1 行で済みます。

同じ金額が 2 件あったときは、条件が > なので 先に出てきたほう が残ります。>= にすると後のほうに入れ替わります。どちらでもよい仕様ですが、決めたら揃えます。

「同じ値だったらどうするか」を決めていないコードは、いつか必ず食い違います。仕様が曖昧なところこそ、自分で決めてコメントに残しておきます。

記録が 1 件もないとき

初回起動や、月が変わった直後は記録が空です。このときの戻り値を決めておかないと、画面が壊れます。

空リストのときは for の中身が 1 度も実行されないので、total0by_category{} のまま、top"" のままになります。remainingbudget - 0 で予算そのものです。上のコードは、初期値を素直に置いているだけで空リストに正しく答えます。

monthly_report([], 3000)

これは {"total": 0, "by_category": {}, "top": "", "remaining": 3000} を返します。

動きを追ってみる

記録が りんご 120円 食費バス 220円 交通費パン 180円 食費 の 3 件、予算が 3000 の場合を追います。

1 件目で total120by_category{"食費": 120}top"りんご" になります。2 件目で total340by_category"交通費" が加わり、220 > 120 なので top"バス" に入れ替わります。3 件目で total520食費120 + 180300 に増え、180220 より小さいので top"バス" のままです。

最後に remaining3000 - 5202480 になり、{"total": 520, "by_category": {"食費": 300, "交通費": 220}, "top": "バス", "remaining": 2480} が返ります。

よくある間違い

  1. remaining を合計の途中で計算する — ループの中で引くと、最後の記録を足す前の値が残ります。引き算はループを抜けたあとです。
  2. top に金額を入れてしまうtop は品名の文字列です。top_price は探すための作業用の変数で、戻り値には入れません。
  3. 予算オーバーを 0 で止める — 使いすぎたときの remaining は負の数のままにします。-500 と出るからこそ、いくら超えたのかが分かります。

やってみよう

関数 monthly_report(records, budget) を実装してください。records は支出の辞書のリスト、budget は予算の整数です。

{"total": 合計金額, "by_category": カテゴリ別の合計の辞書, "top": 最高額の品名, "remaining": 予算から合計を引いた値} の辞書を返します。同じ金額が並んだときは先に出てきたほうを top にします。記録が空のときは top""by_category{} にしてください。

要件

  1. 関数名は monthly_report で、引数は records と budget の2つ
  2. 戻り値は total と by_category と top と remaining の4つのキーを持つ辞書
  3. by_category は同じカテゴリの金額を足し合わせた辞書にする
  4. top は最高額の品名。同額なら先に出てきたほうを採用する
  5. 記録が空のときは top を空文字、by_category を空の辞書、remaining を budget にする
  6. 予算を超えたときも remaining は負の数のまま返す

入出力例

monthly_report([], 3000){"by_category":{},"remaining":3000,"top":"","total":0} monthly_report([{"category":"食費","name":"りんご","price":120},{"category":"交通費","name":"バス","price":220},{"category":"食費","name":"パン","price":180}], 3000){"by_category":{"交通費":220,"食費":300},"remaining":2480,"top":"バス","total":520} monthly_report([{"category":"娯楽","name":"映画","price":1800},{"category":"娯楽","name":"ゲーム","price":2500}], 3000){"by_category":{"娯楽":4300},"remaining":-1300,"top":"ゲーム","total":4300} monthly_report([{"category":"日用品","name":"ノート","price":200},{"category":"日用品","name":"ペン","price":200}], 3000){"by_category":{"日用品":400},"remaining":2600,"top":"ノート","total":400} monthly_report([{"category":"その他","name":"おまけ","price":0}], 3000){"by_category":{"その他":0},"remaining":3000,"top":"おまけ","total":0} monthly_report([{"category":"食費","name":"牛乳","price":240}], 1000){"by_category":{"食費":240},"remaining":760,"top":"牛乳","total":240}

ヒント

生田 陸人
ゆめさくエンジニア / 現役ソフトウェアエンジニア
編集 LuaGate編集部
main.py
main.py
学習モード

メモ

月間レポート機能

⌘S で保存