全機能を統合しよう
どこから手を付けるか
ここからは総合制作です。新しい文法はもう出てきません。これまでに書いてきた関数をつなぎ直して、1 つのアプリとして動かします。
本物のメニューなら、while True で回しながら input() で番号を受け取り、打たれた番号で処理を振り分けます。ただし採点環境にはキーボードがつながっていないので、input() は使えません。そこで、打ち込まれるはずだった操作をあらかじめリストにして引数で渡す 形にします。
やっていることは同じです。input() から 1 行ずつ受け取る代わりに、リストから 1 つずつ取り出すだけの違いで、ループの中身は変わりません。外から来る値を引数で受け取る形にしておくと動作を確かめられる、というのは実務でも同じです。
コマンドは「操作名 + 必要な値」のリストで、3 種類だけ使います。
Python
["add", "りんご", 120, "食費"]
["delete", "りんご"]
["summary"]add は 1 件追加、delete は品名が一致する最初の記録を 1 件だけ削除します。summary と知らない操作名は、記録を変えず何もしません。画面への表示も行わず、集計は最後の戻り値で確かめます。
戻り値は最後の状態をまとめた辞書で、キーは 3 つに固定します。
Python
{"records": [{"name": "りんご", "price": 120, "category": "食費"}], "count": 1, "total": 120}完成済みの部品を呼び分ける
ひな形には _add_record(records, command)、_delete_first(records, name)、_summarize(records) が用意されています。追加の部品はコマンド全体を受け取り、空のカテゴリを "その他" に直して記録を追加します。削除の部品は品名を受け取り、最初の一致だけを削除します。どちらも渡された records 自体を変更するので、戻り値の代入は不要です。集計の部品は最後の状態の辞書を返します。
受講者が書くのは、コマンドを順に取り出して、操作名に応じた部品を呼ぶ部分です。部品を呼んでリストを変更する例を、通知の題材で見てみます。
Python
def add_notice(notices, message):
notices.append(message)
notices = []
request = ["notify", "明日は休館"]
if request[0] == "notify":
add_notice(notices, request[1])
print(notices) # ["明日は休館"]if に当てはまらない操作を無視するなら、else は必要ありません。演習では if と elif で追加と削除だけを扱います。
1 つずつ足して、そのつど確かめる
全部をいっぺんに書こうとすると、どこで間違えたのか分からなくなります。順番はこうします。
まず add だけを処理して、記録が 1 件増えることを確かめます。次に空のコマンド列を渡し、records が空リスト、count と total が 0 で返ることを見ます。ここまでで骨組みができます。
そのうえで削除の部品を呼ぶ分岐を足します。同じ品名が 2 件あれば、先に追加された 1 件だけが消えます。最後に、知らない操作名や summary を混ぜても落ちないことを確かめます。カテゴリが空文字のときに "その他" へ置き換わるかも、ここで見ておきます。
つまずくのは、たいてい 3 か所
1 つ目は、操作名より先に値を取り出してしまうことです。["summary"] は要素が 1 つしかないので、いきなり 2 番目を読むと IndexError になります。操作名で分岐したあと、その枝の中で取り出します。
2 つ目は、削除で全部消してしまうことです。同じ品名が 2 件あるとき、消し続ければ両方消えます。完成済みの _delete_first は、消した時点で break して探索を打ち切ります。探索を書き直す必要はありません。
3 つ目は、合計を追加のたびに作り直すことです。delete が入るとずれます。ひな形の最後の return _summarize(records) が、全部のコマンドを処理したあとの件数と合計を返します。集計を書き直す必要はありません。
やってみよう
関数 run_kakeibo(commands) を実装してください。commands はコマンドのリストです。
add で追加の部品を呼び、delete で削除の部品を呼んで品名が一致する最初の記録を 1 件だけ消し、summary と知らない操作は何もしません。カテゴリが空文字のときは "その他" にします。最後に {"records": 記録のリスト, "count": 件数, "total": 合計金額} の辞書を返します。空のコマンド列なら records は空リスト、count と total は 0 です。各コマンドは空でなく、add と delete には例に示した必要な値がそろって渡されます。補助関数、記録の箱、最後の return はそのまま使います。
要件
- 関数名は run_kakeibo で、引数は commands 1 つだけ。コマンドを順番に処理する
- add は完成済みの _add_record に records とコマンド全体を渡す。空カテゴリの補正もこの部品が行う
- delete は完成済みの _delete_first に records と品名を渡す。最初の一致だけを消し、該当がなければ何もしない
- summary と知らない操作名は何もしない。画面への表示も不要
- 記録の箱と return _summarize(records) はそのまま使う。戻り値は records・count・total を持つ辞書
- コマンドは空でなく、add と delete の必要な値はそろっている。空の commands は空の状態を返す