Problem: On this page, study what Roman numerals are, and then write a function arabic->roman that takes a natural number n in the range 0 < n < 4000 as its input and converts it to a Roman numeral (represented as a string). Additionally, write the reverse function, roman->arabic, which takes a Roman numeral (represented as a string) as its input and converts it to a natural number.

Solution:

#lang racket

(define UNITS '(I II III IV V VI VII VIII IX))
(define TENS '(X XX XXX XL L LX LXX LXXX XC))
(define HUNDREDS '(C CC CCC CD D DC DCC DCCC CM))
(define THOUSANDS '(M MM MMM))
(define VALUES '((I 1) (V 5) (X 10) (L 50) (C 100) (D 500) (M 1000)))

(define (arabic->roman n)
  (define positions (list UNITS TENS HUNDREDS THOUSANDS))
  (define (ar-helper n pos acc)
    (if (= n 0)
        acc 
        (ar-helper (quotient n 10)
                   (+ pos 1)
                   (let ([r (remainder n 10)])
                     (if (zero? r)
                         acc
                         (string-append (symbol->string
                                         (list-ref (list-ref positions pos)
                                                   (- r 1)))
                                        acc))))))       
  (if (<= 1 n 3999)
      (ar-helper n 0 "")
      (error "Error: number out of range!")))


(define (roman->arabic r)
  (define len (string-length r))
  (define (get-value r idx)
    (second (assq (string->symbol (string (string-ref r idx))) VALUES)))
  (define (ra-helper r)
    (let loop ([i 0] [acc 0])
      (if (>= i len)
          acc
          (if (< (+ i 1) len)
              (let ([a (get-value r i)]
                    [b (get-value r (+ i 1))])
                (if (< a b)
                    (loop (+ i 2) (+ acc (- b a)))
                    (loop (+ i 1) (+ acc a))))
              (loop (+ i 1) (+ acc (get-value r i)))))))
  (ra-helper (string-upcase r)))

Now we can test our functions:

> (arabic->roman 39)
"XXXIX"
> (arabic->roman 246)
"CCXLVI"
> (arabic->roman 789)
"DCCLXXXIX"
> (arabic->roman 2421)
"MMCDXXI"
> (arabic->roman 160)
"CLX"
> (arabic->roman 207)
"CCVII"
> (arabic->roman 1009)
"MIX"
> (arabic->roman 1066)
"MLXVI"
> (arabic->roman 3999)
"MMMCMXCIX"
> (arabic->roman 1776)
"MDCCLXXVI"
> (arabic->roman 1918)
"MCMXVIII"
> (arabic->roman 1944)
"MCMXLIV"
> (arabic->roman 2023)
"MMXXIII"
> (roman->arabic "XXXIX")
39
> (roman->arabic "CCXLVI")
246
> (roman->arabic "DCCLXXXIX")
789
> (roman->arabic "MMCDXXI")
2421
> (roman->arabic "CLX")
160
> (roman->arabic "CCVII")
207
> (roman->arabic "MIX")
1009
> (roman->arabic "MLXVI")
1066
> (roman->arabic "MMMCMXCIX")
3999
> (roman->arabic "MDCCLXXVI")
1776
> (roman->arabic "MCMXVIII")
1918
> (roman->arabic "MMXXIII")
2023