【问题标题】:Reading Roman numerals in Haskell在 Haskell 中读取罗马数字
【发布时间】:2012-02-24 02:17:21
【问题描述】:

过去几天我一直在使用 Haskell 数据类型,使用自定义类型来处理罗马数字:

数据 RomanNumeral = I |四 |五 |九 | X |加大码 |大号 | XC | C |光盘 | D |厘米 | M 推导(Eq、Ord、Show、Read)

stringToRomanNumeral :: String -> Maybe [RomanNumeral]
stringToRomanNumeral romString
    |非罗马=没有
    |否则 = 只需 $ map read $ map (\x -> [x]) romStringUpper
    在哪里
        romStringUpper = 映射 C.toUpper romString
        unRoman = any (`notElem` "MDCLXVI") romStringUpper

这很好用,但只能捕获 1 个字符的数字(所以我必须稍后计算 IV、IX 等的值)。

有没有办法read(或reads)输入字符串,使得 Maybe [RomanNumeral] 的返回值也包含 2 个字符的数字?我尝试涉足模式匹配,但我似乎无法正确输入类型。

【问题讨论】:

  • 如果您在数据类型中只有实际数字,例如 I、V、X、C、D、M - 您可以将完整数字表示为这些数字的列表。这会大大简化事情。
  • 我最初的方法仅限于 I、V、X、C、D、M,而 stringToRomanNumeral 函数会返回这些项目。但是将 IV、IX 等作为单独的类型构造函数极大地简化了十进制数的转换(此处未显示)。

标签: haskell


【解决方案1】:

我认为您当前使用 RomanNumeral 数据类型的方式本质上是一个坏主意。

您还应该覆盖显示/读取,而不是依赖默认设置。

也许

-- 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

【讨论】:

  • 我明白了,但我仍然需要将数字符号映射到它们各自的十进制值。拥有一个包含 Int 值的 RomanNumeral 项目并不能解决这个问题(或者更确切地说:我看不出它是怎么做的)。
【解决方案2】:

使用 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)
    ]

它也不是很灵活,任何坏字符都会导致Nothing 结果,但这很容易修改(例如,可以使用catMaybes 而不是sequence 来简单地忽略无效字符)。并且它不检查一般(现代)“递减值”规则(这使得将“IX”解释为 9 而不是 11 成为可能)。但是可以在解析后检查有效性。

【讨论】:

  • IC 和 XD 现在按惯例是不允许的,它们写成 XCIX 和 CDXC。
  • 每隔一天学习一次新东西。谢谢。
  • @RobAgar 据我所知,IV = 4 规则是一个相对较新的发明,罗马人只是将所有数字相加得出总数。
  • 我认为时钟写IIII而不是IV的原因是因为IVX之间的比例为20:4:4。
  • 非常好——非常感谢!我尝试在一个步骤/功能中解析和整理显然是一个坏主意。我确实检查有效性,将整数结果提供给反向函数(convertDecimal)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-17
  • 1970-01-01
  • 1970-01-01
  • 2015-05-16
相关资源
最近更新 更多