Homeの2問目 (Roman Numerals)

週末は家族サービスに追われていてブログすら書けないという状況でした。
とはいえCheckiOは1問くらいやってみました

py.checkio.org

通常使われているアラビア数字をローマ数字(I、II、IV、XIみたいなやつ)に変換する(1〜3,999まで)という問題ですね。

ローマ数字は10進数に基づいていますが、位取りが直接的でなく、ゼロを含める事ができません。 ローマ数字は以下の7つの記号の組み合わせで構成されています。

  • 記号とその値
  • I 1 (unus)
  • V 5 (quinque)
  • X 10 (decem)
  • L 50 (quinquaginta)
  • C 100 (centum)
  • D 500 (quingenti)
  • M 1,000 (mille)

ローマ数字に関する、より詳細な情報は Wikipediaの記事 より参照できます。

 

という事で、wikipediaを見ると

ただし、同じ文字を4つ以上連続で並べることはできない。そのため、例えば 4 は「IIII」、9は「VIIII」とは表現できない。この場合は小さい数を大きい数の左に書き、右から左を減ずることを意味する。これを減算則という。

なので4は「IV」9は「IX」と言った書き方になります。

まず考え方ですが、数字を桁ごとに分解して配列に入れて一つづつ評価するって方向性を検討してみました。
与えられた引数の各桁の数字をそれぞれ「1〜3」、「4〜8」、「9」でパターン化して、桁ごとに代入するローマ数字をかえるみたいな方向性も考えたのですが、それはそれで分岐が多そうだったので桁ごとローマ数字を数字を配列に突っ込んで、それを別の配列に突っ込んであとは元の数字を分解して各桁ごとに評価するってことで、

def checkio(data):
    unus = ["I","II","III","IV","V","VI","VII","VIII","IX"]
    decem = ["X","XX","XXX","XL","L","LX","LXX","LXXX","XC"]
    cetum = ["C","CC","CCC","CD","D","DC","DCC","DCCC","CM"]
    mille = ["M","MM","MMM"]
    digitlist = [unus,decem,cetum,mille]
    datalist = [int(i) for i in list(str(data))]
    Romandigit = ""
    i = 0
    while 0 < len(datalist):
        n = datalist.pop()
        Romandigit = digitlist[i][n-1]+Romandigit
        i = i+1
return Romandigit

って感じでやってみました。

ら、10の時につまづいてしまいました。ケアレスミスですね。
次の桁に行った時は一個前の桁は必要ないので、nが0の時は文字列に追加する処理をスキップしてあげるという形で対処してみました。

def checkio(data):
    unus = ["I","II","III","IV","V","VI","VII","VIII","IX"]
    decem = ["X","XX","XXX","XL","L","LX","LXX","LXXX","XC"]
    cetum = ["C","CC","CCC","CD","D","DC","DCC","DCCC","CM"]
    mille = ["M","MM","MMM"]
    digitlist = [unus,decem,cetum,mille]
    datalist = [int(i) for i in list(str(data))]
    Romandigit = ""
    i = 0
    while 0 < len(datalist):
        n = datalist.pop()
        if n != 0 :
            Romandigit = digitlist[i][n-1]+Romandigit
            i = i + 1
return Romandigit

一旦解けました。
とりあえず、whileよりforだろとか無駄な分岐とかあるので修正しました。

def checkio(data):
    unus = ["","I","II","III","IV","V","VI","VII","VIII","IX"]
    decem = ["","X","XX","XXX","XL","L","LX","LXX","LXXX","XC"]
    cetum = ["","C","CC","CCC","CD","D","DC","DCC","DCCC","CM"]
    mille = ["","M","MM","MMM"]
    digitlist = [unus,decem,cetum,mille]
    datalist = [int(i) for i in list(str(data))]
    datalist.reverse()
    Romandigit = []
    for i, n in enumerate(datalist):
        Romandigit.append(digitlist[i][n])
        Romandigit.reverse()
return ''.join(Romandigit)

無駄なところを削りました。
ただ、悲しいかなこれ以上なんとかすることが難しかったです。
評価が高い回答を見ると

def checkio(n):
    result = ''
    for arabic, roman in zip*1:
        result += n // arabic * roman
        n %= arabic
return result

なるほどなぁ。
見やすいし分かりやすい。
って言ってもzip関数とか色々わからなくて悩んだけど。

n %= arabic

は全く思いつかなかった。

世の中頭の良い人は多いんですね。

*1:1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1),
        'M CM D CD C XC L XL X IX V IV I'.split(