ローマ数字を整数に
IV を素直に足すと 6 になる
ローマ数字は I が 1、V が 5、X が 10、L が 50、C が 100、D が 500、M が 1000 です。左から順に足していけば III は 3、LVIII は 58 と正しく出ます。
ところが IV に同じやり方を使うと 6 になります。正解は 4 です。小さい記号が大きい記号の前に置かれたときだけ、足すのではなく引く。この例外をどう扱うかが、この問題のすべてです。
6 通りの分岐を書き出す前に
引き算になる並びは IV IX XL XC CD CM の 6 つしかありません。ですから「この 6 つを見つけたら特別扱いする」という方針でも解けます。ただしこの道を選ぶと、2 文字ぶんまとめて進める処理と 1 文字ずつ進める処理が混ざり、進める幅の管理でつまずきます。
その前に、6 つの並びに共通している性質を確かめてください。I は V より小さく、X は L より小さい。逆に VI や XI のような通常の並びでは、前の記号のほうが大きいか同じです。つまり記号の名前を 6 通り覚えなくても、隣り合う 2 つの値を比べるだけで、引くのか足すのかが決まります。
例外を列挙する前に、1 つのルールに畳めないかを疑う。この見方はほかの問題でも効きます。
畳めたルールをどう反映させるかは、もう一段の判断です。IV を「4 を足す」と読むこともできますし、「I を引いてから V を足す」と読むこともできます。前者は 2 文字まとめて進める必要があり、後者は 1 文字ずつのままで済みます。1 文字ずつ進む形に統一できれば、進める幅を数える処理そのものが消えます。
ちなみに MCMXCIV は 1994 です。M で 1000、次の C は後ろの M より小さいので引く側、その M は足す側、と順に見ていくと合計が合います。手で 1 回追ってみると、実装の形が見えてきます。
最後の 1 文字には、隣がいない
隣を見る書き方は、必ず末尾で壊れます。LVIII の最後の I を処理するとき、その次の文字は存在しません。
Python
temps = [12, 15, 14, 18]
for i in range(len(temps)):
if i + 1 < len(temps) and temps[i] < temps[i + 1]:
print(i, "翌日のほうが暖かい")範囲の確認を and の左側に置いておくと、右側は安全に評価されます。末尾の要素は条件が成立しないので、自動的に「いつもどおりの扱い」へ落ちます。ローマ数字でも、末尾は必ず足す側になります。
やってみよう
ひな形の TODO を完成させてください。次の値との比較と、加算・減算の選択を自分で書きます。入力を受け取る枠と説明つきの準備部分はそのまま使えます。
演習で使う書き方
本文の気温は次の日と比べる例です。演習でも次の値と比べますが、上がる場合は現在値を引き、それ以外は足します。記号を数へ変える対応表と、次の値を安全に読む部分は用意しました。比較と加算・減算の分岐を書きます。
要件
- 次の値との比較と、加算・減算の選択を自分で実装する
- 関数名と引数、入力と戻り値の形を保つ。説明つきの準備部分を使い、TODO の仮の処理を置き換える