001: 「残業」の解答¶
難易度: ☆
方針¶
買い物かごをそのまま合計すると、同じ商品が複数行に分かれます。 先に商品コードごとの数量を辞書に集計します。
ただし、レシートの明細順は入力順です。 そのため、数量の辞書とは別に、商品コードが初めて出た順番をリストで持ちます。
実装¶
def checkout_receipt(catalog, cart, tax_rate, paid):
quantities = {}
order = []
for code, quantity in cart:
if code not in catalog:
raise KeyError(code)
if code not in quantities:
quantities[code] = 0
order.append(code)
quantities[code] += quantity
lines = []
subtotal = 0
for code in order:
quantity = quantities[code]
line_total = catalog[code] * quantity
lines.append((code, quantity, line_total))
subtotal += line_total
tax = subtotal * tax_rate // 100
total = subtotal + tax
if paid < total:
raise ValueError("paid amount is not enough")
return {
"lines": lines,
"subtotal": subtotal,
"tax": tax,
"total": total,
"change": paid - total,
}
確認¶
catalog = {"tea": 120, "cake": 300, "bag": 5}
cart = [("tea", 2), ("cake", 1), ("tea", 1)]
assert checkout_receipt(catalog, cart, 10, 1000) == {
"lines": [("tea", 3, 360), ("cake", 1, 300)],
"subtotal": 660,
"tax": 66,
"total": 726,
"change": 274,
}
assert checkout_receipt(catalog, [], 10, 0) == {
"lines": [],
"subtotal": 0,
"tax": 0,
"total": 0,
"change": 0,
}
try:
checkout_receipt(catalog, [("tea", 1)], 10, 100)
except ValueError as exc:
assert str(exc) == "paid amount is not enough"
else:
raise AssertionError("ValueError was not raised")
発展¶
数量が0の商品を消す場合は、明細を作るループで quantity == 0 の行を飛ばします。
ただし、負の数量を返品として扱うなら、小計や支払額の意味も決め直す必要があります。