过去几天我一直在使用Haskell数据类型,使用自定义类型处理罗马数字:
data RomanNumeral = I | IV | V | IX | X | XL | L | XC | C | CD | D | CM | M deriving (Eq, Ord, Show, Read) stringToRomanNumeral :: String -> Maybe [RomanNumeral] stringToRomanNumeral romString | unRoman = Nothing | otherwise = Just $ map read $ map (\x -> [x]) romStringUpper where romStringUpper = map C.toUpper romString unRoman = any (`notElem` "MDCLXVI") romStringUpper
这样可以正常工作,但只捕获1个字符数字(因此我必须稍后计算IV,IX等的值)。
有没有办法让read
(或reads
)输入字符串,以使Maybe [RomanNumeral]的返回值也包含2个字符数字?我尝试了模式匹配,但我似乎无法正确使用类型。
答案 0 :(得分:3)
使用reads
效果不佳,因为它预计令牌,它不会分裂,例如"XIV"
"X"
"IV"
和module Roman where
import Data.Char as C
data RomanNumeral = I | IV | V | IX | X | XL | L | XC | C | CD | D | CM | M
deriving (Eq, Ord, Show, Read)
stringToRomanNumeral :: String -> Maybe [RomanNumeral]
stringToRomanNumeral = fmap collate . sequence . map (toRom . C.toUpper)
where
romDict = zip "IVXLCDM" [I,V,X,L,C,D,M]
toRom = flip lookup romDict
collate :: [RomanNumeral] -> [RomanNumeral]
collate (x:ys@(y:zs)) = case lookup (x,y) collationDict of
Just v -> v : collate zs
Nothing -> x : collate ys
collate xs = xs
collationDict :: [((RomanNumeral,RomanNumeral),RomanNumeral)]
collationDict =
[ ((I,V),IV)
, ((I,X),IX)
, ((X,L),XL)
, ((X,C),XC)
, ((C,D),CD)
, ((C,M),CM)
]
获取两个可解析部分,它将整个char序列视为一个标记,因为它们属于同一个字符类。您可以为罗马数字编写自己的解析器(您应该尝试编写解析器很有趣)处理特殊序列。
一种简单的方法是
Nothing
它也不是很灵活,任何不良角色都会导致catMaybes
结果,但这很容易修改(可以使用sequence
代替{{1}}来简单地忽略无效字符,例如)。并且它不检查一般(现代)'递减值'规则(这使得将'IX'解释为9而不是11可能)。然而,在解析之后可以检查有效性。
答案 1 :(得分:0)
我认为您使用RomanNumeral数据类型的当前方式本质上是一个坏主意。
您还应该覆盖show / read,而不是依赖于默认设置。
也许
-- underlying datatype as int
data RomanNumeral = RomanNumeral Int
instance Show RomanNumeral where
show (RomanNumeral x) = -- your code for converting a roman numeral to a string
instance Read RomanNumeral where
readsPred d r = -- your code for reading a string into an integer and then return a RomanNumeral