14
\$\begingroup\$

Your task is to convert a given positive integer from Arabic numeral to Roman numeral.

Things get difficult when you count to 4000.

The romans did this by adding a line above a symbol to multiply that symbol by 1 000. However, overlines aren't exactly displayable in ASCII. Also, there are double overlines to multiply a symbol by 1 000 000, and then triple overline to multiply a symbol by 1 000 000 000, etc...

Therefore, I decided to use parentheses to replace overlines.

The symbols can be individually placed in parentheses. For example, both (VI) and (V)(I) are valid representations of 6 000. (V)M is also a valid representation of 6000.

(I) is a valid way to represent 1 000.

Testcases

Input: 1
Output: I
Input: 2
Output: II
Input: 3
Output: III
Input: 4
Output: IV
Input: 15
Output: XV
Input: 40
Output: XL
Input: 60
Output: LX
Input: 67
Output: LXVII
Input: 400
Output: CD
Input: 666
Output: DCLXVI
Input: 3000
Output: MMM
Input: 3999
Output: MMMCMXCIX
Input: 4000
Output: M(V)
Input: 4999
Output: M(V)CMXCIX
Input: 6000
Output: (VI)
Input: 6000000
Output: ((VI))
Input: 6006000
Output: ((VI)VI)
Input: 6666666666
Output: (((VI)DCLXVI)DCLXVI)DCLXVI

Scoring

This is . Shortest code in bytes win.

\$\endgroup\$
10
  • 1
    \$\begingroup\$ The justification for why this isn't a duplicate clutters the spec. It would be better without it IMO. \$\endgroup\$
    – user45941
    Commented Apr 17, 2016 at 5:51
  • \$\begingroup\$ Where would I add the justification? \$\endgroup\$
    – Leaky Nun
    Commented Apr 17, 2016 at 5:51
  • 1
    \$\begingroup\$ Leave it out. If someone questions whether or not it is a duplicate, have the discussion in comments or in chat. \$\endgroup\$
    – user45941
    Commented Apr 17, 2016 at 5:52
  • \$\begingroup\$ @Mego Done. :-) \$\endgroup\$
    – Leaky Nun
    Commented Apr 17, 2016 at 5:53
  • \$\begingroup\$ Is (IV) an acceptable representation of 4000? \$\endgroup\$
    – Neil
    Commented Apr 17, 2016 at 9:52

8 Answers 8

10
\$\begingroup\$

Mathematica, 67 bytes

Fold["("<>#<>")"<>#2&,RomanNumeral[#~IntegerDigits~1000]/."N"->""]&

Avoids all trouble with M by converting the input to base 1000 and converting each digit separately with RomanNumeral. Then we fold them up by inserting (...) from the left.

Unfortunately, Mathematica represents zeros as N so we need to get rid of those.

\$\endgroup\$
5
  • 1
    \$\begingroup\$ darn mathematica with its builtins for everything >:( \$\endgroup\$
    – AAM111
    Commented Apr 17, 2016 at 18:47
  • 1
    \$\begingroup\$ @OldBunny2800 I'd be surprised if this wasn't beaten by any of the golfing languages anyway though. \$\endgroup\$ Commented Apr 17, 2016 at 19:01
  • \$\begingroup\$ @OldBunny2800 And it costs real money to get it. That's bad. \$\endgroup\$ Commented Apr 18, 2016 at 10:19
  • \$\begingroup\$ @MartinBüttner I thought simply RomanNumeral can do it? \$\endgroup\$
    – Leaky Nun
    Commented Apr 18, 2016 at 12:15
  • 1
    \$\begingroup\$ @KennyLau It outputs MMMM for 4000, it only starts working to spec at 5000 (and then you get the same problem for 4000000 etc.). Even then, it uses overbars instead of parentheses. If you're fine with that, you should say so in the challenge specification. \$\endgroup\$ Commented Apr 18, 2016 at 12:21
9
\$\begingroup\$

JavaScript (ES6), 136 bytes

f=n=>n<4e3?"M1000CM900D500CD400C100XC90L50XL40X10IX9V5IV4I1".replace(/(\D+)(\d+)/g,(_,r,d)=>r.repeat(n/d,n%=d)):`(${f(n/1e3)})`+f(n%1e3)

For numbers under 4000, repeats each Roman "letter" as many times as possible, using the list of Roman "letters" and their decimal values. Otherwise recursively builds up the answer from the division and modulo with 1000. Fortunately repeat truncates so I don't have to do it myself.

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

Common Lisp, 108

(defun p(n)(if(> n 0)(if(< n 4000)(format()"~@R"n)(format()"(~A)~@[~A~]"(p(floor n 1000))(p(mod n 1000))))))

Ungolfed

(defun p(n)
  (if (> n 0)
      (if (< n 4000)

          ;; Built-in Roman formatter (between 1 and 3999)
          (format () "~@R" n)

          ;; Divide N by 1000, as 1000*Q + R.
          ;; First print (p Q) in parentheses (recursively)
          ;; Then, if it is not NIL, the remainder R.
          (format () "(~A)~@[~A~]"
                  (p (floor n 1000))
                  (p (mod n 1000))))))

Tests

Two tests give different outputs than the ones from the question:

(loop for (in out) in '((1 "I")
                        (2 "II")
                        (3 "III")
                        (4 "IV")
                        (15 "XV")
                        (40 "XL")
                        (60 "LX")
                        (67 "LXVII")
                        (400 "CD")
                        (666 "DCLXVI")
                        (3000 "MMM")
                        (3999 "MMMCMXCIX")
                        (4000 "M(V)")
                        (4999 "M(V)CMXCIX")
                        (6000 "(VI)")
                        (6000000 "((VI))")
                        (6006000 "((VI)VI)")
                        (6666666666 "(((VI)DCLXVI)DCLXVI)DCLXVI"))
   for computed = (p in)
   unless (string= out computed)
   collect (list in out computed))

=> ((4000 "M(V)" "(IV)")
    (4999 "M(V)CMXCIX" "(IV)CMXCIX"))
\$\endgroup\$
3
\$\begingroup\$

Ruby, 137 134 130 129 bytes

Recursive function that returns the string. I'm trying to golf down the numeral encodings a bit more if possible, but I'm not sure how.

Whoops, it's practically a direct port of @Neil's ES6 answer now.

f=->x{(x<t=1e3)?"CM900D500CD400C100XC90L50XL40X10IX9V5IV4I1".gsub(/(\D+)(\d+)/){s=x/v=eval($2);x%=v;$1*s}:"(#{f[x/t]})#{f[x%t]}"}

Attempt This Online!

\$\endgroup\$
1
  • 1
    \$\begingroup\$ An anonymous user has suggested a golf: {v=$2.to_i;s=x/v;x%=v;$1*s} to {s=x/(v=eval$2);x%=v;$1*s} (saves 1 byte). \$\endgroup\$
    – rydwolf
    Commented Jan 11, 2022 at 14:27
2
\$\begingroup\$

R, 134

m=1000;x=scan();while(x[1]>=m)x=c(floor(x[1]/m),x[1]%%m,x[-1]);cat(rep("(",length(x)),sep="");cat(as.character(as.roman(x)),sep=")")

It's pretty much not the best option, but I think the idea should go pretty similar to this.

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

Ruby, 185 161 144 bytes

r=->i{i>(t=1e3)? "(#{r[i/t]})"+r[i%t]:(s=?I*i;"IVXXLCCDM".scan(/(.)(.)(.)/){|x,y,z|{x*5=>y,x*4=>x+y,y*2=>z,y+x+y=>x+z}.map{|x,y|s.gsub!x,y}};s)}

Over a year after the original post, I think I learned something about golfing.

Thank you Value Ink for your valuable comments.

\$\endgroup\$
6
  • \$\begingroup\$ gsub can take a string as the first argument, removing the need for substitutions into a regex pattern since s.gsub! x,y does it automatically. Other than that, you can probably just forgo the assignment of your a array since you only use it once, and put it directly into the each_slice call. \$\endgroup\$
    – Value Ink
    Commented Apr 19, 2016 at 4:53
  • \$\begingroup\$ "IVXXLCCDM".scan(/(.)(.)(.)/){|x,b,c|... works too \$\endgroup\$
    – Value Ink
    Commented Jul 10, 2017 at 21:38
  • \$\begingroup\$ Also r[x] is functionally equivalent to r.(x) whenever stabby lambdas are involved \$\endgroup\$
    – Value Ink
    Commented Jul 10, 2017 at 21:40
  • \$\begingroup\$ @ValueInk thank you. That r[x] trick is going to be useful for all my futer recursive golf in ruby! \$\endgroup\$
    – MegaTom
    Commented Jul 10, 2017 at 21:44
  • 1
    \$\begingroup\$ @DialFrost Replacing i>(t=1e3) with i>=4*(t=1e3) would do that. \$\endgroup\$
    – MegaTom
    Commented Dec 8, 2022 at 16:39
2
\$\begingroup\$

TCL 134 bytes

proc f r {
set map {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+}
expr [string map $map $r]0}

Try it here: https://rextester.com/BJC92885

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

Python, 188 194

-6 bytes from getting rid of some whitespace

This challenge brought me back to when I was first learning to program...

def f(x,s=zip("M CM D CD C XC L XL X IX V IV I".split(),[1e3,900,500,400,100,90,50,40,10,9,5,4,1])):
 r=""if x<4e3else"("+f(x/1e3)+")";x%=1e3
 for a,b in s:
    while x>=b:r+=a;x-=b
 return r

It might not be the shortest solution, but I had fun golfing this problem.

Try it out!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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