33
\$\begingroup\$

The challenge is to make any Roman numerals valid code in your chosen language.

They should not appear inside of strings or anything similar, but work just like any other tokens, literals such as (Arabic) numbers, characters or strings; or variable/method/function identifiers, etc.

For instance, in Java, the following would have to compile and run just as if i was initialized to 42:

int i = XLII;

The actual parsing of the numerals is secondary, so you can use a library if you want to, but this is a popularity contest, so creativity is encouraged.

You cannot use any language that actually uses roman numerals, if there is such a thing.

Good luck.

\$\endgroup\$
  • 1
    \$\begingroup\$ So, we need to write an extension to the language, thereby creating a new one? \$\endgroup\$ – Kendall Frey Feb 13 '14 at 15:48
  • 4
    \$\begingroup\$ I'll complain if I want to, because the languages I use are not extensible like that, so I can't even participate. \$\endgroup\$ – Kendall Frey Feb 13 '14 at 16:15
  • 3
    \$\begingroup\$ @KendallFrey The source would have to compile and run. For Java, you could write a "compiler" that edits the source, then programmatically compiles. One way of such compiling is through running a Process \$\endgroup\$ – Justin Feb 13 '14 at 19:04
  • 1
    \$\begingroup\$ Seems boring in most languages. For example in python I would simply write a script that uses ast to parse the source. Insert at the top of the AST the definition of the roman numerals from 1 to 3999. Compile the whole thing and run it. It's just boring to write the code to handle the process. \$\endgroup\$ – Bakuriu Feb 13 '14 at 21:12
  • 2
    \$\begingroup\$ @Bakuriu and your comment is boring too. This is a popularity contest, so you should try to come up with something fun. I think there are some nice answers here that are more imaginative (than compiling a scripting language). \$\endgroup\$ – daniero Feb 13 '14 at 21:19

18 Answers 18

41
\$\begingroup\$

C

There are only so many Roman numerals, since 4000 and higher have no standard notation, and the preprocessor is a wonderful decompression tool, especially if you have no problems with the fact that the code has undefined behaviour.

enum{_
#define a_
#define d_
#define g_
#define j_
#define a(x)c(x)b
#define b(x)c(x)a
#define c(x)x,
#define d(x)f(x)e
#define e(x)f(x)d
#define f(x)m(a(x)(x##I)(x##II)(x##III)(x##IV)(x##V)(x##VI)(x##VII)(x##VIII)(x##IX))
#define g(x)i(x)h
#define h(x)i(x)g
#define i(x)m(d(x)(x##X)(x##XX)(x##XXX)(x##XL)(x##L)(x##LX)(x##LXX)(x##LXXX)(x##XC))
#define j(x)l(x)k
#define k(x)l(x)j
#define l(x)m(g(x)(x##C)(x##CC)(x##CCC)(x##CD)(x##D)(x##DC)(x##DCC)(x##DCCC)(x##CM))
#define m(x)n(x)
#define n(...)__VA_ARGS__##_
m(j()(M)(MM)(MMM))
};

This defines all Roman numerals from I to MMMCMXCIX as enumeration constants, plus _ (which can be replaced by anything you like) as zero.

\$\endgroup\$
  • 12
    \$\begingroup\$ Absolutely brilliant, propose adding it to the next C release (C2X?) as <roman.h>! With %r and %R printf formats! \$\endgroup\$ – Yimin Rong Feb 13 '14 at 21:40
  • 3
    \$\begingroup\$ I thought i know how to use the preprocessor, until now :| Could you update your answer with a minimal usage example of this enum? \$\endgroup\$ – klingt.net Feb 13 '14 at 22:59
  • \$\begingroup\$ @YiminRong And scanf too :) @klingt.net I'm not sure what sort of example you're looking for. A fairly simple one would be int main() { return MMMCMXCIX - M - M - M - CM - XC - IX; } \$\endgroup\$ – hvd Feb 13 '14 at 23:21
  • \$\begingroup\$ I like the idea, but why did you opt to go with undefined behavior when defined behavior was fairly simple? Not judging, just curious? \$\endgroup\$ – CasaDeRobison Feb 14 '14 at 7:19
  • 1
    \$\begingroup\$ @CasaDeRobison For fun, mainly. I like it because it is one of the very few cases of undefined behaviour at preprocessing time that isn't flagged as an error by current compilers, and likely won't be in the future. I don't ever write anything like that in code that's meant to be useful, but code posted here isn't meant to be useful, so what better occasion to try it? \$\endgroup\$ – hvd Feb 14 '14 at 7:44
15
\$\begingroup\$

Ruby

def Object.const_missing(const)
  i = const.to_s
  if i =~ /^[IVXLCDM]+$/
    #http://codegolf.stackexchange.com/questions/16254/convert-arbitrary-roman-numeral-input-to-integer-output
    %w[IV,4 IX,9 XL,40 XC,90 CD,400 CM,900 I,1 V,5 X,10 L,50 C,100 D,500 M,1000].map{|s|k,v=s.split ?,;i.gsub!(k,?++v)}
    eval(i)
  else
    super
  end
end

Any (uppercase) Roman numerals will now be parsed like their decimal equivalents. The only issue is that they're still assignable: you can do X = 9, but not 10 = 9. I don't think there's a way to fix that.

\$\endgroup\$
  • 1
    \$\begingroup\$ This is perfect. The assignment thing is not an issue; You can use assignments to do all sorts of stupid things. \$\endgroup\$ – daniero Feb 13 '14 at 16:10
12
\$\begingroup\$

JavaScript (ES6)

Use Proxy to catch roman numerals.

Testable in Firefox (latest) on JSFiddle.
Not testable in Chrome (with Traceur) since Proxy implementation is broken.

// Convert roman numeral to integer.
// Borrowed from http://codegolf.stackexchange.com/a/20702/10920
var toDecimal = (s) => {
  var n = 0;
  var X = {
    M: 1e3, CM: 900, D: 500, CD: 400, C: 100, XC: 90, 
    L: 50,  XL: 40,  X: 10,  IX: 9,   V: 5,   IV: 4,  I: 1
  };

  s.replace(/[MDLV]|C[MD]?|X[CL]?|I[XV]?/g, (d) => n += X[d]);
  return n;
};

// Whether a string is a valid roman numeral.
var isRoman = (s) => s.match(/^[MDCLXVI]+$/);

// Profixy global environment to catch roman numerals.
// Since it uses `this`, global definitions are still accessible.
var romanEnv = new Proxy(this, {
  get: (target, name) => isRoman(name) ? toDecimal(name) : target[name],
  set: (target, name, value) => isRoman(name) ? undefined : (target[name] = value),
  has: (target, name) => isRoman(name) || name in target,
  hasOwn: (target, name) => name in target
});

Usage:

with (romanEnv) {
  var i = MIV + XLVIII;
  console.log(i); // 1052
}
\$\endgroup\$
11
\$\begingroup\$

C & C++ (Updated Answer)

As observed in a comment, my original solution had two problems:

  1. Optional parameters are only available in C99 and later standards of the language family.
  2. Trailing comma in enum definition is also specific to C99 and later.

Since I wanted my code to be as generic as possible to work on older platforms, I decided to take another stab at it. It is longer than it was before, but it works on compilers and preprocessors set to C89/C90 compatibility mode. All macros are passed an appropriate number of arguments in the source code, though sometimes those macros "expand" into nothing.

Visual C++ 2013 (aka version 12) emits warnings about missing parameters, but neither mcpp (an open source preprocessor that claims high compliance with the standard) nor gcc 4.8.1 (with -std=iso9899:1990 -pedantic-errors switches) emit warnings or errors for those macro invocations with an effectively empty argument list.

After reviewing the relevant standard (ANSI/ISO 9899-1990, 6.8.3, Macro Replacement), I think there is sufficient ambiguity that this should not be considered non-standard. "The number of arguments in an invocation of a function-like macro shall agree with the number of parameters in the macro definition...". It does not seem to preclude an empty argument list as long as the needed parentheses (and commas in the case of multiple parameters) are in place to invoke the macro

As for the trailing comma problem, that is resolved by adding an extra identifier to the enumeration (in my case, MMMM which seems as reasonable as anything for the identifier to follow 3999 even if it doesn't obey the accepted rules of Roman numeral sequencing exactly).

A slightly cleaner solution would involve moving the enum and supporting macros to a separate header file as was implied in a comment elsewhere, and using undef of the macro names immediately after they were used so as to avoid polluting the namespace. Better macro names should undoubtedly be chosen as well, but this is adequate for the task at hand.

My updated solution, followed by my original solution:

#define _0(i,v,x)
#define _1(i,v,x) i
#define _2(i,v,x) i##i
#define _3(i,v,x) i##i##i
#define _4(i,v,x) i##v
#define _5(i,v,x) v
#define _6(i,v,x) v##i
#define _7(i,v,x) v##i##i
#define _8(i,v,x) v##i##i##i
#define _9(i,v,x) i##x

#define k(p,s) p##s,
#define j(p,s) k(p,s)
#define i(p) j(p,_0(I,V,X)) j(p,_1(I,V,X)) j(p,_2(I,V,X)) j(p,_3(I,V,X)) j(p,_4(I,V,X)) j(p,_5(I,V,X)) j(p,_6(I,V,X)) j(p,_7(I,V,X)) j(p,_8(I,V,X)) j(p,_9(I,V,X))
#define h(p,s) i(p##s)
#define g(p,s) h(p,s)
#define f(p) g(p,_0(X,L,C)) g(p,_1(X,L,C)) g(p,_2(X,L,C)) g(p,_3(X,L,C)) g(p,_4(X,L,C)) g(p,_5(X,L,C)) g(p,_6(X,L,C)) g(p,_7(X,L,C)) g(p,_8(X,L,C)) g(p,_9(X,L,C))
#define e(p,s) f(p##s)
#define d(p,s) e(p,s)
#define c(p) d(p,_0(C,D,M)) d(p,_1(C,D,M)) d(p,_2(C,D,M)) d(p,_3(C,D,M)) d(p,_4(C,D,M)) d(p,_5(C,D,M)) d(p,_6(C,D,M)) d(p,_7(C,D,M)) d(p,_8(C,D,M)) d(p,_9(C,D,M))
#define b(p) c(p)
#define a() b(_0(M,N,O)) b(_1(M,N,O)) b(_2(M,N,O)) b(_3(M,N,O))

enum { _ a() MMMM };

#include <stdio.h>

int main(int argc, char** argv)
{
    printf("%d", MMMCMXCIX * MMMCMXCIX);
    return 0;
}

The original answer (which received the first six upvotes, so if no one ever upvotes this again, you shouldn't think my updated solution got the upvotes):

In the same spirit as an earlier answer, but done in a way that should be portable using only defined behavior (though different environments don't always agree on some aspects of the preprocessor). Treats some parameters as optional, ignores others, it should work on preprocessors that don't support the __VA_ARGS__ macro, including C++, it uses indirect macros to ensure parameters are expanded before token pasting, and finally it is shorter and I think easier to read (though it is still tricky and probably not easy to read, just easier):

#define g(_,__) _, _##I, _##II, _##III, _##IV, _##V, _##VI, _##VII, _##VIII, _##IX,
#define f(_,__) g(_,)
#define e(_,__) f(_,) f(_##X,) f(_##XX,) f(_##XXX,) f(_##XL,) f(_##L,) f(_##LX,) f(_##LXX,) f(_##LXXX,) f(_##XC,)
#define d(_,__) e(_,)
#define c(_,__) d(_,) d(_##C,) d(_##CC,) d(_##CCC,) d(_##CD,) d(_##D,) d(_##DC,) d(_##DCC,) d(_##DCCC,) d(_##CM,)
#define b(_,__) c(_,)
#define a       b(,) b(M,) b(MM,) b(MMM,)
enum { _ a };
\$\endgroup\$
  • 1
    \$\begingroup\$ +1, but note that the use of empty macro arguments, and the comma at the end of an enumerator list, are both new features of C99 and C++11, and both C99 and C++11 do support __VA_ARGS__. \$\endgroup\$ – hvd Feb 14 '14 at 17:08
  • \$\begingroup\$ Dang if you're not right! I guess I've been seeing this used as extensions all this time. Ah well. :) \$\endgroup\$ – CasaDeRobison Feb 15 '14 at 5:25
8
\$\begingroup\$

Common Lisp

The following is a rather lengthy explanation of how I made a macro that you can use like this:

(roman-progn
  (+ XIV XXVIII))

When a macro is called in Common Lisp, it basically acts like a function, only that the arguments are received before they are evaluated. Actually, since in Common Lisp code is just data, what we receive is a (nested) list representing an unparsed syntax tree that we can do whatever we want with, and it's done in compile-time.

Helper functions

The first step of the plan is to take this tree and scan it for anything that looks like Roman Numerals. This being Lisp and all, let's try to do it somewhat functionally: We need a function that will do a deep traversal of a tree and return every object for which a provided function searchp returns true. This one is even (semi) tail-recursive.

(defun deep-find-all (searchp tree &optional found)
  (if (null tree)
    found
    (let* ((item (car tree))
           (new-found (if (consp item)
                        (deep-find-all searchp item found)
                        (if (funcall searchp item)
                          (cons item found)
                          found))))

      (deep-find-all searchp (cdr tree) new-found))))

Then some code for parsing the roman numerals, courtesy of Rosetta Code:

(defun mapcn (chars nums string)
  (loop as char across string as i = (position char chars) collect (and i (nth i nums))))

(defun parse-roman (R)
  (loop with nums = (mapcn "IVXLCDM" '(1 5 10 50 100 500 1000) R)
        as (A B) on nums if A sum (if (and B (< A B)) (- A) A)))

The actual macro

We take the syntax tree (body), search it with our deep-find-all procedure and somehow make the roman numerals that we find, available.

(defmacro roman-progn (&body body)
  (let* ((symbols (deep-find-all (lambda (sym)
                                   (and
                                     (symbolp sym)
                                     (loop for c across (string sym)
                                           always (find c "IVXLCDM")))) body))
         (parsed-romans (mapcar (lambda (sym)
                                  (list sym (parse-roman (string sym)))) symbols)))

    (if parsed-romans
      `(let (,@parsed-romans)
         ,@body)  
      `(progn
         ,@body))))

So, what is 1 + 2 + 3 + (4 * (5 + 6)) + 7 ?

(roman-progn
  (+ I II III (* IV (+ V VI)) VII))
=> 57

And to see what actually happened when the macro was invoked:

(macroexpand-1 +)
=> (LET ((VII 7) (VI 6) (V 5) (IV 4) (III 3) (II 2) (I 1))
   (+ I II III (* IV (+ V VI)) VII))
\$\endgroup\$
7
\$\begingroup\$

Lua

setmetatable(_G, {
    __index = function(_, k)
        if k:match"^[IVXLCDM]*$" then
            local n = 0
            for _, v in ipairs{{IV = 4}, {IX = 9}, {I = 1}, {V = 5}, {XL = 40}, {X = 10}, {XC = 900}, {CD = 400}, {CM = 900}, {C = 100}, {D = 500}, {M = 1000}} do
                local p, q = next(v)
                local r
                k, r = k:gsub(p, "")
                n = n + r * q
            end
            return n
        end
    end
})

Simply a fallback __index for the global table. The actual conversion using gsub turned out much prettier than I imagined it to be.

\$\endgroup\$
5
\$\begingroup\$

Smalltalk (Smalltalk/X) (87/101 chars)

of course we could easily modify the parser's tokenizer (as it is part of the class library, and as such open for modification, and always present), but a challenge is to affect only evaluations in a given context, so that the rest of the system works as usual.

Version 1:

define a number of variables in the evaluation namespace. So this will affect interactive doIts (aka evals):

(1 to:3999) do:[:n | 
    Workspace workspaceVariables at:(n romanPrintString asUppercase) put:n]

then I can do (in a doIt, but not in compiled code):

   |a b|
   a := MMXIV.
   b := V.
   a + b

-> 2019

Notice: the 101 chars includes whitespace; actually it can be done with 87 chars.
Also notice, when define in the global Smalltalk namespace, I'd see those constants also in compiled code.

Version 2:

Use a methodWrapper hook, which allows for any existing code to be wrapped without recompiling. The following wraps the Parser's tokenizer to look for a roman identifier to be scanned and makes it an integer. The tricky part is to dynamically detect if the calling context is from the roman empire or not. This is done using a query signal (which is technically a proceedable exception):

define the query:

InRomanScope := QuerySignal new defaultAnswer:false.

So we can ask at any time ("InRomanScope query") to get false by default.

Then wrap the scanner's checkIdentifier method:

MessageTracer 
    wrapMethod:(Scanner compiledMethodAt:#checkForKeyword:)
    onEntry:nil
    onExit:[:ctx :ret |
        InRomanScope query ifTrue:[
            |scanner string token n|

            scanner := ctx receiver.
            string := ctx argAt:1.
            (n := Integer readFromRomanString:string onError:nil) notNil
            ifTrue:[
                scanner "/ hack; there is no setter for those two
                    instVarNamed:'tokenType' put:#Integer;
                    instVarNamed:'tokenValue' put:n.
                true
            ] ifFalse:[
                ret
            ].
        ] ifFalse:[
            ret
        ]
    ].

Now the scanner works as usual, unless we are in the roman empire:

InRomanScope answer:true do:[
    (Parser evaluate:'MMDXXV') printCR.
].

-> 2525

we can even compile code:

Compiler 
    compile:'
        inTheYear2525
            ^ MMDXXV
    '
    forClass:Integer.

nice try; but this fails with a syntax error (which is exactly what we want). However, in the roman empire, we CAN compile:

InRomanScope answer:true do:[

    Compiler 
        compile:'
            inTheYear2525
                ^ MMDXXV
        '
        forClass:Integer.
]

and now, we can ask any integer (sending that message) from inside and outside of Rome:

(1000 factorial) inTheYear2525

-> 2525

\$\endgroup\$
  • \$\begingroup\$ Nice to see Smalltalk! \$\endgroup\$ – Yimin Rong Feb 13 '14 at 21:48
5
\$\begingroup\$

Postscript

I tried to follow the C one but I didn't understand it. So I did it this way:

/strcat{
    2 copy length exch length add string % 1 2 3 
    3 2 roll % 2 3 1 
    2 copy 0 exch putinterval % 2 3 1 
    2 copy length % 2 3 1 3 n(1)
    5 4 roll putinterval % 3 1 
    pop 
}def
/S{
    dup length string cvs 
} def 
[
/u {/ /I /II /III /IV /V /VI /VII /VIII /IX}
/t {/ /X /XX /XXX /XL /L /LX /LXX /LXXX /XC}
/h {/ /C /CC /CCC /CD /D /DC /DCC /DCCC /CM}
/m {/ /M /MM /MMM /MMMM}
>>begin
[0
[
[m]{ % M*
    S   
    [h]{ % M* H*
        S
        [t]{ % M* H* T*
            S
            [u]{ % M* H* T* U*
                S
                4 copy
                strcat strcat strcat % M* H* T* U* M*H*T*U*
                5 1 roll % M*H*T*U* M* H* T* U*
                pop % (M*H*T*U*)* M* H* T*
            }forall
            pop % (M*H*T*U*)** M* H*
        }forall
        pop % (M*H*T*U*)*** M*
    }forall
    pop % (M*H*T*U*)****
}forall
]
{exch dup 1 add}forall pop>>end begin

Postscript doesn't have enum but we can construct a dictionary with sequential integer values and fold them into an array. This reduces the problem to generating all the strings in sequence, which is done by concatenating in 4 nested loops. So it generates all the strings, then interleaves each string with an increasing counter value, resulting in a long series of <string> <int> pairs on the stack which are wrapped in <<...>> to produce a dictionary object.

The program constructs and installs a dictionary mapping all names for the roman numerals to their corresponding value. So mentioning the names in source text invokes the automatic name-lookup and yields the integer value on the stack.

II IV MC pstack

prints

2
4
600
\$\endgroup\$
4
\$\begingroup\$

Haskell, using meta-programming in Template Haskell and roman-numerals:

{-# LANGUAGE TemplateHaskell #-}
import Data.Char (toLower)
import Language.Haskell.TH
import Text.Numeral.Roman

$( mapM (\n -> funD (mkName . map toLower . toRoman $ n)
                    [ clause [] (normalB [| n |]) []]) $ [1..1000 :: Int] )

main = print xlii

Haskell reserves identifiers starting with upper case letters for constructors, so I used lower-case.

\$\endgroup\$
4
\$\begingroup\$

J - 78 char

This only goes up to MMMCMXCIX = 3999, as with the other solutions.

(}.,;L:1{M`CDM`XLC`IVX('';&;:(_1,~3#.inv]){' ',[)&.>841,3#79bc5yuukh)=:}.i.4e3

Breaking it down (recall J is usually read from right to left, unless superseded by parentheses):

  • M`CDM`XLC`IVX - Four boxes of letters. We're going to use numeric arrays into index into these letters and build up subwords of Roman numerals.
  • 841,3#79bc5yuukh - This is the numeric data, tightly encoded.*
  • (_1,~3#.inv]) - This will decode the above data, by expanding in ternary and appending -1.
  • ('';&;:(...){' ',[)&.> - Pairing up the numbers on the left with the boxes on the right (&.>), decode the arrays of numbers and use them to index into the letters. We treat 0 as space by prepending a space character to the letter lists. This procedure builds lists of words like I II III IV V VI VII VIII IX and M MM MMM.
  • { - Take the Cartesian product of these four boxes full of words. Now we have a 4D array of all the Roman numerals.
  • }.,;L:1 - Run all that into a single 1D list of Roman numerals, and remove the empty string at the front because it would create an error. (L: is a rare sight in J golf! Usually there are not this many levels of boxing involved.)
  • }.i.4e3 - The integers from 0 to 4000, excluding the endpoints.
  • Finally, we put everything together with a global assignment =:. J allows you to have a boxed list of names on the LHS, as a form of computed multiple assignment, so this works out fine.

Now the J namespace is full of variables representing Roman numerals.

   XCIX
99
   MMCDLXXVIII
2478
   V * LXIII   NB. 5*63
315
   #4!:1]0     NB. How many variables are now defined in the J namespace?
3999

* I need the number 2933774030998 to later be read in base 3. It so happens that I can express it in base 79 using digits no greater than 30, which is good because J can only understand digits up to 35 (0-9 and then a-z). This saves 3 characters over decimal.

\$\endgroup\$
3
\$\begingroup\$

Python

The Idea is simple as the other answers. But Just to be neat and not to pollute the global namespace, a context manager is used. This also imposes the restriction, that you need to declare before hand, the extent of Roman numerical you are planning to use.

Note Just to keep it simple, and not to reinvent the wheel, I have utilized the Roman python package

Implementation

class Roman(object):
    memo = [0]
    def __init__(self, hi):
        import rome
        if hi <= len(RomanNumericals.memo):
            self.romans = Roman.memo[0:hi]
        else:
            Roman.memo += [str(rome.Roman(i))
                    for i in range(len(Roman.memo),
                            hi+1)]
            self.romans = Roman.memo
    def __enter__(self):
        from copy import copy
        self.saved_globals = copy(globals())
        globals().update((v,k) for k,v in enumerate(self.romans[1:], 1))
    def __exit__(self,*args ):
        globals().clear()
        globals().update(self.saved_globals)

Demo

with Roman(5):
    with Roman(10):
        print X
    print V
    print X


10
5

Traceback (most recent call last):
  File "<pyshell#311>", line 5, in <module>
    print X
NameError: name 'X' is not defined
\$\endgroup\$
3
\$\begingroup\$

Python

This is possibly the simplest solution using Python:

ns = [1000,900,500,400,100,90,50,40,10,9,5,4,1]
ls = 'M CM D CD C XC L XL X IX V IV I'.split()
for N in range(1, 4000):
    r=''
    p=N
    for n,l in zip(ns,ls):
        while p>=n:
            r+=l
            p-=n
    exec('%s=%d'%(r,N))


i, j = XIX, MCXIV
print i, j
\$\endgroup\$
  • 3
    \$\begingroup\$ Better to use globals()[var] = value than exec(). \$\endgroup\$ – Ramchandra Apte Feb 15 '14 at 15:21
3
\$\begingroup\$

D

using D's compile time function evaluation

import std.algorithm;
string numerals(){
    auto s = cartesianProduct(["","I","II","III","IV","V","VI","VII","VIII","IX"], 
                              ["","C","CC","CCC","CD","D","DC","DCC","DCCC","CM"],
                              ["","X","XX","XXX","XL","L","LX","LXX","LXXX","XC"],
                              ["","M","MM","MMM"]);
    s.popFront();//skip first
    char[] result="enum ";
    int i=1;
    foreach(p;s){
        result~=p[3]~p[2]~p[1]~p[0]~"="~i++~",";
    }
    result[$-1]=';';//replace last , with a ;
    return result.idup;
}
mixin(numerals());
\$\endgroup\$
3
\$\begingroup\$

APL (Dyalog APL), 77 bytes

Prompts for Roman numeral maximum length and defines all variables.

t←'IVXLCDM',⊂⍬
{⍎⍕⍵'←',+/2(⊣ׯ1*<)/0,⍨(∊1 5∘ר10*⍳4)[t⍳⍵]}¨,/t[⍉8⊥⍣¯1⍳¯1+8*⎕]

t←t gets

'IVXLCDM', Roman chars followed by

 an enclosed

 empty list

t[] index t with…

 the transposed (to get the right order)

8⊥⍣¯1 appropriate width base-eight representation of

 the first n indices, where n is

¯1+ one less than

8*⎕ eight to the power of numeric input

,/ flatten rows (each representation)

{ apply the following anonymous function on each representation…

()[t⍳⍵] corresponding to the positions of the argument's items in t, select from…

   the enlisted

  1 5∘ר one and five times each of

  10* ten to the power of

  ⍳4 zero through three

0,⍨ append zero

2(…)/ on each length-two sliding window, apply the following anonymous function train…

  ⊣× the left argument times

  ¯1* negative one to the power of

  < whether the left argument is less than the right argument

+/ sum

⍵'←', prepend the argument (the Roman numeral) and an assignment arrow

 format (to flatten and convert the number to text)

 execute that (makes the assignment outside the anonymous function)

Try it online! (using max-length 5)

\$\endgroup\$
2
\$\begingroup\$

PHP

There a several rules for valid roman numbers

  1. Write the greatest value befor the lower values

  2. Subtract only [I,X,C] before the next 2 greater values

  3. Subtract double [I,X,C] before the next 2 greater values

  4. Subtract double [I,X,C] before the greater values

  5. Combine 4+5

Online Version

Step 1 Create the rules

{"M":1000,"IM":999,"IIM":998,"XM":990,"XXM":980,"CM":900,"CCM":800,"D":500,"ID":499,"IID":498,"XD":490,"XXD":480,"CD":400,"C":100,"IC":99,"IIC":98,"XC":90,"XXC":80,"L":50,"IL":49,"IIL":48,"XL":40,"X":10,"IX":9,"IIX":8,"V":5,"IV":4,"I":1}

is the JSON output for all valid roman numbers

$rule_sub_all=$rule_add=$rule_sub_simple=["M"=>1000,"D"=>500,"C"=>100,"L"=>50,"X"=>10,"V"=>5,"I"=>1];
foreach(["I","X","C"]as$roman_letter){
    $rule_sub_simple[$roman_letter.array_search($value=$rule_add[$roman_letter]*5,$rule_add)]=$value-$rule_add[$roman_letter];
    $rule_sub_simple[$roman_letter.array_search($value=$rule_add[$roman_letter]*10,$rule_add)]=$value-$rule_add[$roman_letter];
}
$rule_sub_lower_one=$rule_sub_double=$rule_sub_simple;
foreach(["I","X","C"]as$roman_letter){
    $rule_sub_double[$roman_letter.$roman_letter.array_search($value=$rule_add[$roman_letter]*10,$rule_add)]=$value-2*$rule_add[$roman_letter];

foreach($rule_add as$key=>$value){
    if($value>$rule_add[$roman_letter])$rule_sub_all[$roman_letter.$key]=$value-$rule_add[$roman_letter];
    if($value>5*$rule_add[$roman_letter])$rule_sub_all[$roman_letter.$roman_letter.$key]=$value-2*$rule_add[$roman_letter];
    if($value>10*$rule_add[$roman_letter])$rule_sub_lower_one[$roman_letter.$key]=$value-$rule_add[$roman_letter];
    }
}
arsort($rule_sub_all);
arsort($rule_sub_lower_one);
arsort($rule_sub_double);
arsort($rule_sub_simple);

Step 2 Make lists for all rules till 3999

$array_sub_lower_one=$array_sub_double=$array_sub_all=$array_add=$array_sub_simple=[];
for($i=1;$i<4000;$i++){
    $number_sub_all=$number_add=$number_sub_simple=$number_sub_double=$number_sub_lower_one=$i;
    $roman_letter_sub_all=$roman_letter_add=$roman_letter_sub_simple=$roman_letter_sub_double=$roman_letter_sub_lower_one="";
    foreach($rule_sub_all as$key=>$value){
        $roman_letter_sub_all.=str_repeat($key,$d=$number_sub_all/$value^0);$number_sub_all-=$value*$d;
        if(in_array($value,$rule_sub_lower_one)){
        $roman_letter_sub_lower_one.=str_repeat($key,$d=$number_sub_lower_one/$value^0);$number_sub_lower_one-=$value*$d;}    
        if(in_array($value,$rule_add)){
        $roman_letter_add.=str_repeat($key,$d=$number_add/$value^0);$number_add-=$value*$d;}
        if(in_array($value,$rule_sub_simple)){
        $roman_letter_sub_simple.=str_repeat($key,$d=$number_sub_simple/$value^0);$number_sub_simple-=$value*$d;}
        if(in_array($value,$rule_sub_double)){
        $roman_letter_sub_double.=str_repeat($key,$d=$number_sub_double/$value^0);$number_sub_double-=$value*$d;}
     }
    $array_sub_lower_one[$roman_letter_sub_lower_one]=$i;   
    $array_sub_all[$roman_letter_sub_all]=$i;
    $array_add[$roman_letter_add]=$i;
    $array_sub_simple[$roman_letter_sub_simple]=$i;
    $array_sub_double[$roman_letter_sub_double]=$i;
}

Step 3 Create constants

Combine all lists and define constants

foreach(array_merge($array_add,$array_sub_simple,$array_sub_lower_one,$array_sub_double,$array_sub_all)as$key=>$value){ define($key,$value); }

Output

In the example mutiply two valid versions of the number 8

echo IIX *  VIII;
\$\endgroup\$
  • \$\begingroup\$ Nice, but how do I use this? I'm not fluent in PHP; Could you please give an example of how this enables me to write Roman numerals in the code? \$\endgroup\$ – daniero Mar 17 '17 at 19:02
  • \$\begingroup\$ @Daniero Now the code should work \$\endgroup\$ – Jörg Hülsermann Mar 17 '17 at 21:16
  • \$\begingroup\$ Ah, that's better :) \$\endgroup\$ – daniero Mar 18 '17 at 12:52
1
\$\begingroup\$

Rebol

Rebol []

roman-dialect: func [
    {Dialect which allows Roman numerals as numbers (integer!)}
    block [block!]
    /local word w m1 m2 when-in-rome rule word-rule block-rule
  ][
    when-in-rome: does [
        if roman? w: to-string word [change/part m1 roman-to-integer w m2]
    ]

    word-rule:  [m1: copy word word! m2: (when-in-rome)]
    block-rule: [m1: any-block! (parse m1/1 rule)]
    rule:       [any [block-rule | word-rule | skip]]

    parse block rule
    do block
]

; couple of helper functions used in above parse rules

roman-to-integer: func [roman /local r eval] [
    r: [IV 4 IX 9 XL 40 XC 90 CD 400 CM 900 I 1 V 5 X 10 L 50 C 100 D 500 M 1000]
    eval: join "0" copy roman
    foreach [k v] r [replace/all eval k join " + " v]
    do replace/all to-block eval '+ '+
]

roman?: func [s /local roman-chars] [
    roman-char: charset "IVXLCDM"
    parse/case s [some roman-char]
]


Example

roman-dialect [
    m: X + V
    print m
    print M + m  ; doesn't confuse the variable m with Roman Numeral M
    if now/year = MMXIV [print "Yes it is 2014"]
    for n I V I [
        print [rejoin [n "+" X "="] n + X]
    ]
]

Output:

15

1015

Yes it is 2014

1+10= 11

2+10= 12

3+10= 13

4+10= 14

5+10= 15


Disclaimer: I'm sure there are other (and probably better!) ways of doing this in Rebol as well.

PS. My roman-to-integer function is a transliteration of histocrat's nice Ruby algorithm for converting Roman Numeral string into a number. Returned with thanks! +1

\$\endgroup\$
1
\$\begingroup\$

Lua

local g={}
setmetatable(_G,g)
local romans = {I = 1,
                V = 5,
                X = 10,
                L = 50,
                C = 100,
                D = 500,
                M = 1000}
local larger = {    -- Precalced, for faster computing.
                I = "VXLCDM",
                V = "XLCDM",
                X = "LCDM",
                L = "CDM",
                C = "DM",
                D = "M",
                M = " " -- A space will never be found, and it prevents the pattern matcher from erroring.
}
local storedRomans = {}
function g:__index(key)
    if storedRomans[key] then
        return storedRomans[key]
    end
    if key:match"^[IVXLCDM]+$" then
        local n = 0
        local i = 1
        for s in key:gmatch"." do
            local N = romans[s]
            if key:find('['..larger[s]..']',i) then
                n = n - N
            else
                n = n + N
            end
            i = i + 1
        end
        storedRomans[key] = n
        return n
    end
end

This effects the metatable of the global table, giving it a new index function. When a global variable which only contains roman numerals is asked for, eg XVII, it parses it.

Easy to test;

print(XVII) -- 42
print(VI)   -- 6
print(III)  -- 3
print(MMM)  -- 3000

Try it online!

\$\endgroup\$
1
\$\begingroup\$

VBA, 204 bytes

A declared subroutine which takes no input, and when run, creates the publicly accessible Enum, R, which contains all of the Roman Numeral values. These values may be used directly, without referencing the Enum.

Enum hold values from 1 to 3999.

Note: Terminal "s on lines 3 and 7 are included for syntax highlighting only, and do not contribute to the bytecount

Public Sub i
Set c=ThisWorkbook.VBProject.VBComponents.Add(1).CodeModule
c.AddFromString"Public Enum R"
For n=1To 3999
c.AddFromString WorksheetFunction.Roman(n)&"="&n
Next
c.AddFromString"End Enum"
End Sub

Ungolfed and Explained

Public Sub InitializeRomanNumerals()
    Dim c As CodeModule                                            ''  Dim CodeModule
    Set c=ThisWorkbook.VBProject.VBComponents.Add(1).CodeModule    ''  Create Module
    Let c.Parent.Name = "RomanNumeral_Module"                      ''  Name the Module
    Call c.AddFromString("Public Enum RomanNumerals")              ''  Declare the Enum
        For n=1To 3999Step 1                                       ''  Iter from 1 to 3999
            Call c.AddFromString(WorksheetFunction.Roman(n)&"="&n) ''  Add the Roman
                                                                   ''  Numeral to Enum
        Next n                                                     ''  Loop
    Call c.AddFromString("End Enum")                               ''  End The Enum
End Sub
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.