Roman Numeral parsing in Haskell ================================ Copyright 2006 Ben Clifford. Based on Java code by the same author developed sometime around 2000. Licensed under a BSD-like license. > import System.Environment Each Roman numeral has a specific value: > numerals = [ > ('I', 1), > ('V', 5), > ('X', 10), > ('L', 50), > ('C', 100), > ('D', 500), > ('M', 1000) > ] Given a string of characters (eg. "MMIV") we can easily map this to a list of corresponding integers (eg. [1000, 1000, 1, 5]). > numeralsToIntegers s = map numeralToInteger s To do this, we make use of the numeralToInteger helper function, which looks up a single numeral in the numerals table. If the numeral doesn't exist, it throws an error. > numeralToInteger c = let > r = lookup c numerals in > case r of > Nothing -> error ("Unknown roman numeral: "++[c]) > Just x -> x The basic algorithm is simply to sum the digits. However, this does not fully support the style of writing that is common in the latter day. It can handle (for example) III or MMX, but not VI or MCM. > numeralsToIntegerAdditive s = foldl1 (+) (numeralsToIntegers s) To handle the full range of inputs, we need to check whether a large number is preceeded by a smaller number, and in this case perform a subtraction. In other cases, perform an addition as before. > numeralsToIntegerSubtractive s = foldr (+-) (0,0) (numeralsToIntegers s) This code makes use of the +- operator, defined here. It passes the value of the last processed digit from right to left as the second element of a pair. > preceeding +- (sum,succeeding) = > if preceeding>=succeeding > then (sum+preceeding,preceeding) > else (sum-preceeding,preceeding) To extract the final value, we can get the accumulator from the returned pair and discard the last processed digit (which will be the first digit of our roman numeral string). > numeralsToInteger s = let (x,_) = (numeralsToIntegerSubtractive s) in x Now all we need is a simple main routine to drive it all. > main = do > args <- getArgs > print $ map numeralsToInteger args Use it like this: $ ghc Hrn.lhs $ ./a.out MCM XXX XIX VI V MCMXCVII [1900,30,19,6,5,1997]