罗马数字

作者:Andrei Osipov

https://projecteuler.net/problem=89

为了使用罗马数字书写的数字被认为是有效的,必须遵循一些基本规则。尽管这些规则允许一些数字以多种方式表达,但始终有一种“最佳”的方式来书写特定数字。

例如,数字十六似乎至少有六种写法

IIIIIIIIIIIIIIII

VIIIIIIIIIII

VVIIIIII

XIIIIII

VVVI

XVI

但是,根据规则,只有 XIIIIII 和 XVI 是有效的,最后一个例子被认为是最有效的,因为它使用的数字最少。

11K 的文本文件 roman.txt 包含一千个用有效的但并非最简化的罗马数字书写的数字;有关此问题的明确规则,请参阅关于...罗马数字。

找出通过以最简化的形式书写每个数字可以节省的字符数。

注意:您可以假设文件中的所有罗马数字都不包含超过四个连续的相同单位。

源代码:prob089-andreoss.pl

use v6;

use experimental :cached;

multi roman-to-int() { 0 }
multi roman-to-int(Str $r where $r.chars > 1 ) {
    roman-to-int(| $r.comb)
}

multi roman-to-int('I', 'X', |a) { 9   + roman-to-int |a }
multi roman-to-int('I', 'V', |a) { 4   + roman-to-int |a }
multi roman-to-int('I',      |a) { 1   + roman-to-int |a }

multi roman-to-int('X', 'L', |a) { 40  + roman-to-int |a }
multi roman-to-int('X', 'C', |a) { 90  + roman-to-int |a }
multi roman-to-int('X',      |a) { 10  + roman-to-int |a }

multi roman-to-int('C', 'M', |a) { 900 + roman-to-int |a }
multi roman-to-int('C', 'D', |a) { 400 + roman-to-int |a }
multi roman-to-int('C',      |a) { 100 + roman-to-int |a }

multi roman-to-int($v,       |a) {
    roman-to-int(|a) + do given $v {
        when 'I' { 1 }
        when 'V' { 5 }
        when 'X' { 10 }
        when 'L' { 50 }
        when 'C' { 100 }
        when 'D' { 500 }
        when 'M' { 1000 }
    }
}


sub int-to-roman(Int \n) returns Str is cached {
    given n {
        when * >= 1000 { 'M'  ~ int-to-roman(n - 1000) }
        when * >= 900  { 'CM' ~ int-to-roman(n - 900) }
        when * >= 500  { 'D'  ~ int-to-roman(n - 500) }
        when * >= 400  { 'CD' ~ int-to-roman(n - 400) }
        when * >= 100  { 'C'  ~ int-to-roman(n - 100) }
        when * >= 90   { 'XC' ~ int-to-roman(n - 90) }
        when * >= 50   { 'L'  ~ int-to-roman(n - 50) }
        when * >= 40   { 'XL' ~ int-to-roman(n - 40) }
        when * >= 10   { 'X'  ~ int-to-roman(n - 10) }
        when * >= 9    { 'IX' ~ int-to-roman(n - 9) }
        when * >= 5    { 'V'  ~ int-to-roman(n - 5) }
        when * >= 4    { 'IV' ~ int-to-roman(n - 4) }
        when * >= 1    { 'I'  ~ int-to-roman(n - 1) }
        default        { '' }
    }
}


sub MAIN(Bool :$run-tests = False,
         Str  :$file      = $*SPEC.catdir($*PROGRAM-NAME.IO.dirname, 'roman.txt')) {

    return TEST if $run-tests;
    die "$file is missing" unless $file.IO.e;
    say [+] do for $file.IO.lines -> $line {
        $line.chars - (int-to-roman roman-to-int $line).chars;
    }
}

sub TEST {
    use Test;

    {
        my $i = roman-to-int("XXXXVIIII");
        ok roman-to-int(int-to-roman $i) == $i, "sanity test";
    }
    {
        my $i = (^1000).pick;
        ok roman-to-int(int-to-roman $i) == $i, "sanity test";
    }

    ok (roman-to-int("IIIIIIIIIIIIIIII") ==
        roman-to-int("VIIIIIIIIIII") ==
        roman-to-int("VVIIIIII") ==
        roman-to-int("XIIIIII") ==
        roman-to-int("VVVI") ==
        roman-to-int("XVI")), 'roman-to-int works';
    is int-to-roman(6666) , "MMMMMMDCLXVI", 'int-to-roman works';
    done;
}