Scalaで「アラビア数字・ローマ数字変換」

お題はこちら。

お題:アラビア数字・ローマ数字変換 - No Programming, No Life

テストは割愛。

ご覧のとおり、

  • アラビア数字→ローマ数字の場合は、大きい単位から順に元の数字から引いていって、引けた記号を積むだけ
  • ローマ数字→アラビア数字の場合は、左側から大きい順に並んでいると仮定して、その分の数字を加算していくだけ

という単純なもの。減算則はについては、該当する数字(900とか400とか)を二文字で一つの記号として扱うことで解決。

ようやく再帰を使ったコーディングにも慣れてきたけど、あんまり面白いコードは思いつかないなあ。

object ArabicRoman {

    val codeTable = List(
        (1000, "M"), (900, "CM"), (500, "D"), (400, "CD"), (100, "C"),
        (90, "XC"), (50, "L"), (40, "XL"), (10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I"))

    def arabicToRoman(src: Int): String = {

        require((src >= 1) && (src <= 3999))

        def convert(left: Int, cont: String = "", code: List[(Int, String)] = codeTable): String = {
            val (unitVal, unitChar) = code.head
            left - unitVal match {
                case n if (n == 0) => cont + unitChar
                case n if (n > 0)  => convert(n, cont + unitChar, code)
                case _             => convert(left, cont, code.tail)
            }
        }
        convert(src)
    }

    def romanToArabic(src: String): Int = {

        require(src != null && src.nonEmpty)
        val p = src.toUpperCase()
        require("""[^MDCLXVI]""".r.findFirstMatchIn(p) == None)

        def convert(left: String, cont: Int = 0, code: List[(Int, String)] = codeTable): Int = {
            val (unitVal, unitChar) = code.head
            left.splitAt(unitChar.length) match {
                case ("", _)            => cont
                case (`unitChar`, tail) => convert(tail, cont + unitVal, code)
                case _                  => convert(left, cont, code.tail)
            }
        }
        convert(p)
    }
}

(8/24 追記)

もしかして、引数の領域が定められていて、領域外の場合はエラー、みたいな場合は、アサーションにかけるよりPartialFunctionにすべきなのかも。

ということでPartialFunction版。ガードが長いとちと汚い。

object ArabicRoman {

    type =?>[A, B] = PartialFunction[A, B]

    val codeTable = List(
        (1000, "M"), (900, "CM"), (500, "D"), (400, "CD"), (100, "C"),
        (90, "XC"), (50, "L"), (40, "XL"), (10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I"))

    val arabicToRoman: (Int) =?> String = {
        case src if (src >= 1 && src <= 3999) => {

            def convert(left: Int, cont: String = "", code: List[(Int, String)] = codeTable): String = {
                val (unitVal, unitChar) = code.head
                left - unitVal match {
                    case n if (n == 0) => cont + unitChar
                    case n if (n > 0) => convert(n, cont + unitChar, code)
                    case _ => convert(left, cont, code.tail)
                }
            }
            convert(src)
        }
    }

    val romanToArabic: (String) =?> Int = {
        case src if (Option(src).exists{s => {s.nonEmpty && ("""[^MDCLXVI]""".r.findFirstMatchIn(s.toUpperCase) == None)}}) => {

            def convert(left: String, cont: Int = 0, code: List[(Int, String)] = codeTable): Int = {
                val (unitVal, unitChar) = code.head
                left.splitAt(unitChar.length) match {
                    case ("", _) => cont
                    case (`unitChar`, tail) => convert(tail, cont + unitVal, code)
                    case _ => convert(left, cont, code.tail)
                }
            }
            convert(src.toUpperCase())
        }
    }
}