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
If you’d like some more practice, there’s a Project Euler problem that also deals with Roman numerals:
https://projecteuler.net/problem=89
It would be interesting to see if you could make a minimal toolset to accomplish both prompts.
I just wrote about that topic the other day. It’s surprisingly difficult. eisl/library/roman.lsp at master · sasagawa888/eisl (github.com)
I’m pleasure to be of some help. https://medium.com/@kenichisasagawa/lisp-roman-numerals-97fb0fb5e519