Converting Decimal to Roman Numbers in Perl 6

This week’s (week 10) Perl Request Challenge, challenge 1, was:

Write a script to encode/decode Roman numerals. For example, given Roman numeral CCXLVI, it should return 246. Similarly, for decimal number 39, it should return XXXIX. Checkout wikipedia page for more informaiton.

For this blog, I’m going to talk about converting a decimal to a Roman numeral.

To start with, I read the Wikipedia page referenced in the challenge, and realized there were several different systems for writing Roman numerals – it wasn’t as standardized as I thought! That said, I stuck with the style used in the description of the challenge, specifically “subtractive” notation.  Essentially, the symbols are written from the largest value to the smallest value, left to right, with no more than 3 of any symbol used.  When four of a symbol would normally be used (for instance, IIII to mean 4), instead it would be written as IV, meaning one less than 4 (you see this because the smaller number is before the bigger number).

So that’s what I’ll talk about below – the part of the code that converts an integer to a Roman number.

sub decimal-to-roman(UInt:D $decimal is copy -->Str:D) {
    my $str = '';

    my @conversions =
        M  => 1000,
        CM =>  900,
        D  =>  500,
        CD =>  400,
        C  =>  100,
        XC =>   90,
        L  =>   50,
        XL =>   40,
        X  =>   10,
        IX =>    9,
        V  =>    5,
        IV =>    4,
        I  =>    1,
    ;

    for @conversions -> $pair {
        while $decimal ≥ $pair.value {
            $decimal -= $pair.value;
            $str     ~= $pair.key;
        }
    }

    return $str;
}

I haven’t talked about a lot of the syntax of the basic language in past posts about the Perl Weekly Challenge, but I’ll mention a bit here.

In line 1 above, we define a function named decimal-to-roman. Yes, you can use “-” in function names, which is probably anxiety provoking to most people familiar with Algol-based languages, but is unambiguous in Perl 6 syntax – and thus allowed (indeed, Lisp, among many others allow hyphens too).

This function takes one parameter, $decimal.  This variable has a type – UInt:D.  The UInt part is relatively straightforward to most developers, whether or not they use Perl 6 – must be an unsigned integer.  The :D part is unusual to most non-Perl 6 developers, but is simple – it simply indicates the UInt passed must be defined, I.E. it has to be an actual value.  This line 1 also makes this parameter, $decimal, a copy.  By default, Perl 6 parameters are read only, so they can’t be modified. This saves the overhead of making a copy when calling functions.  Finally, the function returns a Str:D – a defined string.

You don’t have to define types in Perl 6 – but when you do, you get some automatic error checking, so it’s sometimes a good thing to do.  For instance, if I tried to return a UInt rather than a String, or returned an undefined variable, I would get an error that indicating where the assignment happened, not some far away module that expected a String and didn’t get it.

We’re using $str to build a string that will be returned by this function, so we initialize it to zero character long string. We’ll be building this string left to right later in the function.

We then build an array of Pair. In Perl 6, pairs are built-in to the language. Pairs are simply two values stored together, typically referred to as a key and a value.  We use these pairs to represent some symbols and their decimal values.  You can define a pair with the X => Y notation (X is the key, Y is the value).

We start with the largest values – for instance, “M”, which represents 1,000 (this is the largest symbol in widespread usage.

The next value is “CM”.  “C” means 100, so “CM” means 100 less than 1000, or 900.

We then represent the rest of the pairs of Roman numeral “digits” and their associated decimal values.

The loops are where it gets interesting. For each pair, we see if that pair is less than the current value of $decimal.  For instance, if $decimal is 8, the first time through this outer loop we have the pair representing “M” and 1000.  The inner loop only runs while $decimal is equal or greater than 1000 (my editor automatically changes >= to ≥, a Unicode character, to save characters on my screen – and let me keep more state on the screen).  Of course, it’s not ≥ 1000, so we go on to the next symbol, “CM”, which is still bigger than 8, etc.

Eventually we get to “V”, representing 5. So the first (and only) time through the inner loop with a pair of V => 5, we subtract 5 from $decimal (so $decimal is now 3), and add the symbol “V” to the end of $str (which now contains “V”).

We then move to “IV”, representing 4, which is not ≥ the value of $decimal (3).

Then we get to I => 1.  The inner loop runs 3 times, subtracting 1 from $decimal each time and appending an “I” to $src each time.  After the 3rd run through this inner loop, $decimal is 0, which is no longer greater or equal to 1.  So we’re done.

So that’s really all there is to it, and this is an algorithm that is generalization to many different languages (I also provided a Perl 5 solution).

One thought on “Converting Decimal to Roman Numbers in Perl 6

Leave a comment