私自身が表題の問題を解いた時のプログラムについて解説します。
問題の詳細は「バスの料金を計算しよう(ややリアル編)」(CodeIQ)を参照してください。
幼児 | 子供 | 大人 | |
---|---|---|---|
記号 | I | C | A |
料金 | 後述 | 大人の半額 (10円未満切り上げ) |
標準料金 |
通常 | 定期券あり | 特別割引 | |
---|---|---|---|
記号 | n | p | x |
料金 | 年齢区分の通り | 無料 | 通常の乗客の 56% (10円未満の端数は切り上げ) |
乗客記号 | An | Ax | Cn | In | Cx | Ix |
---|---|---|---|---|---|---|
一日乗車券の値段 | 910円 | 510円 | 460円 | 460円 | 260円 | 260円 |
Pythonで解答しています。
import fileinput import math #特別料金を計算する def calcSpecialPrice(i): return 10 * math.ceil(i*56/1000) # 子供料金を計算する def CalcChildPrice(lst): ret = [] for i in lst: n = 10 * math.ceil((i // 10) / 2) ret.append(n) return ret # 入力文字列をパースする def parseLine(str): ret = [] price, passenger = str.split(":") ret.append(passenger.split(",")) ret.append(list(map(int, price.split(",")))) ret.append(CalcChildPrice(ret[1])) return ret # 乗客の大人、子供、幼児の数を数える def countPassenger(lst): p = {"An":0, "Ax":0, "Ap":0, "Cn":0, "Cx":0, "Cp":0, "In":0, "Ix":0, "Ip":0} for k,v in p.items(): p[k] = len(list(filter(lambda a:a==k, lst))) return p # 1日乗車券を考慮して大人、子供、幼児の値段表を計算する def calcPriceDic(ap, cp): price_dic = { "An": sum(ap), "Ax": sum(list(map(calcSpecialPrice, ap))), "Ap": 0, "Cn": sum(cp), "Cx": sum(list(map(calcSpecialPrice, cp))), "Cp": 0, "In": sum(cp), "Ix": sum(list(map(calcSpecialPrice, cp))), "Ip": 0, } oneday_pass = { "An": 910, "Ax": 510, "Ap": 0, "Cn": 460, "Cx": 260, "Cp": 0, "In": 460, "Ix": 260, "Ip": 0, } for k,v in price_dic.items(): if price_dic[k] > oneday_pass[k]: price_dic[k] = oneday_pass[k] return price_dic # 金額計算 # p 乗客を数えたもの # ap 大人の金額 # cp 子供の金額 def calcTotal(p, ap, cp): price_dic = calcPriceDic(ap, cp) # 通常料金の幼児を相殺する cnt_i = p["In"] cnt_a = 2*(p["An"] + p["Ax"] + p["Ap"]) if (cnt_i - cnt_a) < 0: cnt_i = p["In"] + p["Ix"] p["In"] = 0 if (cnt_i - cnt_a)<0: p["Ix"] = 0 else: p["Ix"] = cnt_i - cnt_a else: p["In"] = (cnt_i - cnt_a) ret = 0 for k, v in p.items(): ret += v * price_dic[k] return ret #============================================================================== # main #------------------------------------------------------------------------------ for line in fileinput.input(): if not line.strip(): continue PassengerList, PliceListA, PliceListC = parseLine(line.strip()) PassengerCount = countPassenger(PassengerList) total = calcTotal(PassengerCount, PliceListA, PliceListC) print(total)
初級編の問題があるのでそちらをやっていれば基本的な処理の手順は同じです。ただし、条件がかなりややこしくなっています。これをいかにシンプルに扱えるかがポイントになります。
もう一点、浮動小数点の誤差もポイントになります。
parseLine()がパース処理、CalcChildPrice()が子供料金の計算です。
やっていることは初級編と同じです。
ポイントとしてはAn、Ax、Apのように年齢区分と料金区分をひとまとめにして別物として扱ってしまうことでしょう。A(大人)のグループがあってその中にn、x、pが含まれるような扱いよりも計算時の処理がシンプルになります。もっと種別(n、x、pのシリーズ)が多い場合はクラス化して扱うことを検討しますが、それでも別物として扱うことには違いがありません。
乗員のリスト(文字のリスト)をAn、Ax、Ap、・・・ごとの人数のディクリョナリに変換します。
1日乗車券のことを考慮して、入力に対応する区分ごとの料金表を計算します。
引数のapは入力値をパースして作った大人の標準料金のリスト、cpは子供の標準料金のリストです。これを合計すればそれぞれの標準料金になります。
35〜45行目で料金表を作っています。この中で使用しているcalcSpecialPrice()は特別割引を計算する関数です。この計算は浮動小数点の誤差を考慮しなければなりません。私はそれで1回リジェクトされています。最初の計算式は次の通りでした。
10 * math.ceil((i // 10) * 0.56)
この式では浮動小数点誤差が出るパターンがあって実際の値よりもごくわずかだけ大きな値になります。そのため、ceil()の結果が1大きくなってしまいます。
47〜57行目の値は1日パスの料金です。
59〜61行目の処理は計算した料金と1日パスの料金を比較し、1日パスの値段が安ければそちらに変更するという処理をして、入力値に対する値段表を作成します。
calcTotal()で最終的な計算をします。
基本的に初級編と同じなのですが、幼児の人数を大人と相殺する処理が面倒くさくなっています。値段を安くするため、標準料金>特別料金の順で相殺しなければなりません。その処理を73〜85行目でやっています。
大人の数はAn、Ax、Apの合計ですので、その2倍が相殺可能な幼児の人数です。幼児の数はまずInだけを考慮し、Inが大人の数×2以上ならInから大人の数×2を引いて終わりです。
Inが大人の数×2より少ない場合、まずInを0にし相殺されなかった大人の数をIxから引くことになりますが、コードではちょっと工夫しています。Inの数(p["In"])は0にするのですが、幼児の数InとIxを合計してから大人の数×2と比較しています。式を変形しただけですがこちらの方が少し単純な処理になります。
後は初級編と同じように区分ごとの人数と料金を掛け合わせて合計すれば終わりです。
実際の業務にありそうな問題とのことでしたが、確かに条件によってこういうこまごまとした処理が必要なことはよくあります。こういう時は前準備でデータを整えて可能な限り単純なループで処理できるようにするのがポイントと思っています。
このプログラムの場合は最終的な計算は88〜89行目のループですが非常に単純です。もし、ここに条件文が含まれていたりするとデバッグが大変ですし、修正も大変です。この問題程度の複雑さなら大したことはないかもしれませんが、実際の業務で作るプログラムの場合、何か修正したら別のところに問題が出ることにすらなり得ます。