As a *mostly-newbie ***Haskell / **fp programmer (*one* *who only read **http://learnyouahaskell.com/*) I wanted to exercise my functional coding skills, by resolving *non-helloworld* programming challenges. So I’ve tried something classical:

Write a program that will convert an integer in the range of [1 .. 3,888,000] to the equivalent roman numeral:

The symbols are: (

I =1); (V= 5); (X= 10); (L= 50); (C= 100); (D= 500); (M= 1,000); (upperscoreV= 5,000); (upperscoreX= 10,000); (upperscoreL= 50,000); (upperscoreC= 100,000); (upperscoreD= 500,000); (upperscoreM= 1,000,000) .Rules:

- If a letter if followed by another with equal or lesser value, the two numbers are added (eg. XX = 20, LV = 55);
- If a letter is followed by another with greater value, the first is subtracted from the second (eg. IV = 4, XC = 90);
Examples:

The numbers from [1..10]:

I,II,III,IV,V,VI,VII,VIII,IX,X

Other numbers:CDLVI= 456 ,MCDXLIV= 1444 ,MMMI= 3001 , etc.

*and now…* **The solution (one of them, probably far from the best)**

The main idea was to divide my solution into small reusable blocks.

**1**. First I’ve written a function, **figures**, that transforms a given number into a (reverse) list of figures. For example, 1113 becomes [3,1,1,1]**, **1234 becomes [4,3,2,1] and so on…

1 2 3 |
figures :: Int -> [Int] figures 0 = [] figures x = x `mod` 10 : figures (x `div` 10) |

**2.** Then I defined a list (of Strings), romans, containing all the roman numerals. The upperscored versions were coded using an underscore :), like (upperscore **X**) = _X = 10, 000 .

1 |
romans = ["I", "V", "X", "L", "C", "D", "M","_V","_X","_L","_C","_D","_M"] |

**3.** Secondly I had to write a function that finds the roman numeral equivalent for a certain figure based on figure’s context. This function was called figureEq and receives two input parameters: the order and the figure.

1 2 3 4 5 6 7 8 |
figureEq :: Int -> Int -> String figureEq ord x | x == 0 = [] | x < 4 = concat $ replicate x $ romans !! ord | x == 4 = (romans !! ord) ++ (romans !! (ord + 1)) | x < 9 = (romans !! (ord+1)) ++ concat (replicate (x-5) $ romans !! ord) | x == 9 = (romans !! ord) ++ (romans !! (ord+2)) | otherwise = "" |

The order will influence the returning roman numeral equivalent. For example:

1 2 3 4 5 6 7 |
"III" Prelude>figureEq 2 3 "XXX" Prelude>figureEq 4 3 "CCC" Prelude>figureEq 6 3 "MMM" |

**4.** The next step was to write two functions that make use of the** figureEq **function . I called them **generatorRomOrd** and **generatorRomRaw** . The idea was simple: recurse over the figures of the number that is going to be converted, apply **figureEq** with the right order on the current figure, and append the resulting equivalents into a String.

1 2 3 4 5 6 7 8 9 10 11 12 |
{-- Transforms a number into a list of roman numerals starting with an order --} generatorRomOrd :: Int -> [Int] -> String generatorRomOrd ord [x] = figureEq ord x generatorRomOrd ord (x:xs) = generatorRomOrd (ord+2) xs ++ figureEq ord x {-- Transforms a number into a list of roman numerals starting with order 0 --} generatorRomRaw :: Int -> String generatorRomRaw nr | 0 < nr && nr <= 3888000 = generatorRomOrd 0 (figures nr) | otherwise = error "Invalid number: 0 < nr <= 3,888,000 ." |

The** generatorRomRaw** function will be able to convert any number between [0 .. 3,888,000] from the Arab notation to the corresponding Roman notation. The resulting String is still in a raw form (the upperscaped letters still use underscore) .

1 2 3 4 |
generatorRomRaw 1322 "MCCCXXII" Prelude>generatorRomRaw 432121 "_C_D_X_X_XMMCXXI" |

**5.** At this step I needed to somehow represent the upperscored letters . The easiest solution was to represent the resulting raw String (“_C_D_X_X_XMMCXXI” ) on two console rows. The upper row will contain only underscores, and the second row will contain the raw String without any “_” .

1 2 3 4 5 6 7 8 9 10 |
{-- Returns string generated by 'generatorRomRaw' without '_' --} lowerString :: String -> String lowerString = filter (/='_') {-- Returns the upper string --} upperString :: String -> String upperString [] = [] upperString (x:xs) | x == '_' = x : upperString (tail xs) | otherwise = ' ' : upperString xs |

**6. **The last step was to combine all functions written so far into a “real” solution.

1 2 3 4 5 6 7 |
solution :: IO() solution = do putStrLn "Please enter the number to convert (0 < n <= 3,888,000): " number <- getLine putStrLn ("Roman: n" ++ upperString (generatorRomRaw (read number::Int)) ++ "n" ++ lowerString (generatorRomRaw (read number::Int))) |

And the working solution:

1 2 3 4 5 6 |
Prelude>solution Please enter the number to convert (0 < n <= 3,888,000): 999999 Roman: ____ _ CMXCMXCMXCIX |

Of course you can take a look at a more elegant implementation: here .

The full code:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
{-- Returns the Roman notation for a number. author: Andrei Ciobanu --} romans = ["I", "V", "X", "L", "C", "D", "M","_V","_X","_L","_C","_D","_M"] -- [ 1, 5, 10, 50, 100, 500, 1000] {-- Transforms a number into a (reverse) list of figures.Eg. 1113 -> [3,1,1,1] --} figures :: Int -> [Int] figures 0 = [] figures x = x `mod` 10 : figures (x `div` 10) {-- Generates the equivalent o a given figure and it's order. --} figureEq :: Int -> Int -> String figureEq ord x | x == 0 = [] | x < 4 = concat $ replicate x $ romans !! ord | x == 4 = (romans !! ord) ++ (romans !! (ord + 1)) | x < 9 = (romans !! (ord+1)) ++ concat (replicate (x-5) $ romans !! ord) | x == 9 = (romans !! ord) ++ (romans !! (ord+2)) | otherwise = "" {-- Transforms a number into a list of roman numerals starting with an order --} generatorRomOrd :: Int -> [Int] -> String generatorRomOrd ord [x] = figureEq ord x generatorRomOrd ord (x:xs) = generatorRomOrd (ord+2) xs ++ figureEq ord x {-- Transforms a number into a list of roman numerals starting with order 0 --} generatorRomRaw :: Int -> String generatorRomRaw nr | 0 < nr && nr <= 3888000 = generatorRomOrd 0 (figures nr) | otherwise = error "Invalid number: 0 < nr <= 3,888,000 ." {-- Returns string generated by 'generatorRomRaw' without '_' --} lowerString :: String -> String lowerString = filter (/='_') {-- Returns the upper string --} upperString :: String -> String upperString [] = [] upperString (x:xs) | x == '_' = x : upperString (tail xs) | otherwise = ' ' : upperString xs solution :: IO() solution = do putStrLn "Please enter the number to convert (0 < n <= 3,888,000): " number <- getLine putStrLn ("Roman: n" ++ upperString (generatorRomRaw (read number::Int)) ++ "n" ++ lowerString (generatorRomRaw (read number::Int))) |