Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change

Comment on

( #3333=superdoc: print w/replies, xml ) Need Help??

The dark art of unearthing magic formulae to improve your golf score was highlighted recently by thospel during the Fonality Golf Challenge.

During the recent follow-up codegolf Roman to Decimal game, I explored the other side of this Roman coin, experimenting with a number of different magic formulae to convert a Roman letter to its corresponding arabic number.

This meditation describes my first attempt at cooking up a golfic magic formula, then further offers, for your enjoyment, a simple golf challenge drawn from that experience.

In many golf games, some of the more alluring approaches turn out to be too long to be competitive. During the recent Fonality game, for instance, Daniel Tuijnman's 135.51 and tybalt89's 155.32 were among the most fascinating of the solutions, yet they proved too lengthy to be serious contenders.

So it proved for me during the codegolf Roman to Decimal game where all my shortest solutions were fairly boring, not employing any magic formulae at all. I doubt, therefore, that the magic formulae presented below would be of much practical use in that game ... unless, of course, you find a dramatic improvement on what I've done.

The Challenge

The mapping of each Roman letter to its corresponding arabic number is shown in the table below:


The challenge is to write a subroutine that takes a single valid Roman letter (I,V,X,L,C,D,M) in $_ and returns the corresponding Arabic (or Scientific) representation.

Example Solution

An example solution is:

# 12345678901234567890123456789012345 sub r{1+4*y/VLD//.E.(3*/M/+2*/C|D/+/X|L/)}
The score is the number of characters in the subroutine body, 35 for this example.

Different Approaches

There are at least two fundamental approaches to this problem:

  • A formula. In this approach, illustrated in the example solution above, you calculate the arabic number from a formula containing the Roman letter.
  • A lookup table. Here, you lookup the arabic number corresponding to a Roman letter from some sort of lookup table or data structure. AFAICT, this approach does seem to produce the shortest solutions. I won't discuss this approach further in this meditation.

The Magic Formula Approach

There's just no sane regularity in this thing. But in "random" mappings with a very small result set like this, the shortest solution is often to make up some magic formula that has no particular meaning, but just happens to give the wanted result.

-- Ton Hospel explaining his original decimal-to-roman magic formula

In the example solution above, notice that the last bit, namely:

maps each Roman letter to a digit as follows:


How to shorten this code? Since a %4 modulo operation always produces a digit in the required 0-3 range, an obvious approach is to concoct some sort of magic formula ending in %4.

Brute Force Search Programs

To find such a formula requires a program to search for the elusive magic formula from the vast number of possible ones. Here was my first such program:

for my $m (1 .. 999) { for my $n (1 .. 99) { for my $p (4 .. 9) { $M = ord(M)x3%$m%$n%$p; $D = ord(D)x3%$m%$n%$p; $C = ord(C)x3%$m%$n%$p; $L = ord(L)x3%$m%$n%$p; $X = ord(X)x3%$m%$n%$p; $V = ord(V)x3%$m%$n%$p; $I = ord(I)x3%$m%$n%$p; if ($M==3 && $D==2 && $C==2 && $L==1 && $X==1 && $V==0 && $I==0) + { print "m=$m n=$n p=$p: M=$M D=$D C=$C L=$L X=$X V=$V I=$I\n"; } } } }

These sort of brute force search programs tend to be a bit slow in Perl. And they're not hard to write in C. Accordingly, I rewrote this Perl program in C as follows:

#include <stdio.h> #define magic(v,m,n,p) ((v) % (m) % (n) % (p)) #define MATCH_M(M,D,C,L,X,V,I) ( (M)==3 \ && (D)==2 && (C)==2 \ && (L)==1 && (X)==1 \ && (V)==0 && (I)==0) int main(int argc, char* argv[]) { int m, n, p; int I, V, X, L, C, D, M; for (m = 1; m <= 999; ++m) { for (n = 1; n <= 99; ++n) { for (p = 4; p <= 9; ++p) { M = magic(777777, m, n, p); // 777777 is ord(M)x3 D = magic(686868, m, n, p); // 686868 is ord(D)x3 C = magic(676767, m, n, p); // 676767 is ord(C)x3 L = magic(767676, m, n, p); // 767676 is ord(L)x3 X = magic(888888, m, n, p); // 888888 is ord(X)x3 V = magic(868686, m, n, p); // 868686 is ord(V)x3 I = magic(737373, m, n, p); // 737373 is ord(I)x3 if (MATCH_M(M,D,C,L,X,V,I)) { printf("m=%d n=%d p=%d: M=%d D=%d C=%d L=%d X=%d V=%d I=%d\n +", m, n, p, M, D, C, L, X, V, I); } } } } return 0; }

Becoming aware that you've stopped playing Perl golf and are now instead writing little C programs to search for magic formulae is an odd experience. :-) Anyway, running either of these programs produces:

m=75 n=50 p=4: M=3 D=2 C=2 L=1 X=1 V=0 I=0 m=734 n=13 p=4: M=3 D=2 C=2 L=1 X=1 V=0 I=0 m=737 n=92 p=5: M=3 D=2 C=2 L=1 X=1 V=0 I=0
which tells you that there are three magic formulae, the shortest of which is the shortest line above, corresponding to Perl code of:
Thankfully, this is 3 strokes shorter than the original non-magic:

Now that was the first magic formula that popped into my head. There are many, many other possible formulae. How to find the shortest one? I have no idea, but, much to my annoyance, I've now found five others the same length as the original above ... but none shorter. Here are the six shortest I've found so far:

sub m1{1+4*y/VLD//.E.ord()x3%75%50%4} sub m2{1+4*y/VLD//.E.ord()*49/3%54%4} sub d1{1 .E.ord()*39%73%7%5>>y/VLD//} sub d2{1 .E.ord()*38/9%62%4>>y/VLD//} sub d3{1 .E.~-ord()*41%52%5>>y/VLD//} sub d4{1 .E.(8^ord)*29%65%4>>y/VLD//}
Update: Found three that are one stroke shorter:
sub d5{1 .E.(72^ord)*5/7%5>>y/VLD//} sub d6{1 .E.(6^ord)%72%7%4>>y/VLD//} sub d7{1 .E.(76^2+ord)%7%5>>y/VLD//}
and two that are three strokes shorter:
sub d8{1 .E.(3^77%ord)%7>>y/VLD//} sub m8{5**y/VLD//.E.(42^88*ord)%5}

Notice that the solutions ending in >>y/VLD// above divide by two rather than multiply by five and therefore have a slightly different mapping, namely:


Can you find a shorter magic formula?


References Added Later

In reply to Golf: Magic Formula for Roman Numerals by eyepopslikeamosquito

Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":

  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.
  • Log In?

    What's my password?
    Create A New User
    and all is quiet...

    How do I use this? | Other CB clients
    Other Users?
    Others cooling their heels in the Monastery: (6)
    As of 2018-05-23 05:34 GMT
    Find Nodes?
      Voting Booth?