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 followup 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:
Roman  Arabic  Scientific 

I  1  1E0 
V  5  5E0 
X  10  1E1 
L  50  5E1 
C  100  1E2 
D  500  5E2 
M  1000  1E3 
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:
The score is the number of characters in the subroutine body, 35 for this example.# 12345678901234567890123456789012345 sub r{1+4*y/VLD//.E.(3*/M/+2*/CD/+/XL/)}
Different Approaches
There are at least two fundamental approaches to this problem:
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 decimaltoroman magic formula
In the example solution above, notice that the last bit, namely:
maps each Roman letter to a digit as follows:3*/M/+2*/CD/+/XL/
Roman  Digit 

I  0 
V  0 
X  1 
L  1 
C  2 
D  2 
M  3 
How to shorten this code? Since a %4 modulo operation always produces a digit in the required 03 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:
which tells you that there are three magic formulae, the shortest of which is the shortest line above, corresponding to Perl code of: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
Thankfully, this is 3 strokes shorter than the original nonmagic:ord()x3%75%50%4
3*/M/+2*/CD/+/XL/
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:
Update: Found three that are one stroke shorter: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//}
and two that are three strokes 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//}
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:
Roman  Digit 

I  0 
V  1 
X  1 
L  2 
C  2 
D  3 
M  3 
Can you find a shorter magic formula?
References
References Added Later


Replies are listed 'Best First'.  

Re: Golf: Magic Formula for Roman Numerals
by ambrus (Abbot) on Feb 18, 2007 at 11:21 UTC  
by eyepopslikeamosquito (Chancellor) on Feb 18, 2007 at 12:29 UTC  
by ambrus (Abbot) on Feb 18, 2007 at 12:56 UTC  
Re: Golf: Magic Formula for Roman Numerals
by bart (Canon) on Feb 18, 2007 at 18:51 UTC 