1

所以,我有一个问题表的有趣实践练习:将整数(小于 5000)转换为罗马数字。这是我写的代码;但是,我在 GHCI 中加载脚本时遇到了困难(输入“=”时出现解析错误)。有任何想法吗?

units, tens, hundreds, thousands :: [String]
units=["I", "II", "III", "IV", "V", "VI", "VII", "IIX", "IX"]
tens=["X", "XX", "XXX", "XL", "L", "LX", "LXX", "XXC", "XC"]
hundreds=["C", "CC", "CCC", "CD", "D", "DC", "DCC", "CCM","CM"]
thousands=["M", "MM", "MMM", "MV", "V"]

combine :: (Int,Int,Int,Int) -> String
combine (0,0,0,u)   = units !! u
combine (0,0,t+1,0) = tens !! t
combine (0,0,t+1,u) = tens !! t ++ units !! u
combine (0,h+1,0,0) = hundreds !! h
combine (0,h+1,t+1,0) = hundreds !! h ++ tens !! t
combine (0,h+1,t+1,u)   = hundreds !! h ++ tens !! t ++ units !! u
combine (f+1,0,0,0) = thousands !! f
combine (f+1,h+1,0,0)   = thousands !! f ++ hundreds !! h
combine (f+1,h+1,t+1,0) = thousands !! f ++ hundreds !! h ++ tens !! t
combine (f+1,h+1,t+1,u) = thousands !! f ++ hundreds !! h ++ tens !! t ++ units !! u
4

3 回答 3

2

There are actually several syntax errors in this program (Edit: thanks to @Lukasz's edits, now there's only one syntax error). But the one you're asking about is caused by the fact that you can't just create a binding in ghci. Where in a program you write

a = 1

in ghci you must write

let a = 1

otherwise you will get the parse error on input `=' error.

I would recommend you to put your program in a file and compile it with ghc or run it with runhaskell instead of inserting lets, it'll be more convenient for future work and bugfixing.

于 2013-10-30T12:07:55.210 回答
0

我编写了一个模块来处理整数和罗马数字之间的转换。但是,我的模块有 3 个缺点。

  1. 我的模块可以处理的最大罗马数字不会超过 4999,因为我假设最大的罗马单位是“M”,而“MMMM”按照规则不是有效的罗马数字。

  2. 我没有在函数“findKey”中使用 Maybe 来避免意外键,因为我没有很好地掌握 applicative、functor 和 monad。

  3. 我不知道如何完成 prettyRoman2,它将在罗马数字的字符串中查找组合“VIV”、“LXL”和“DCD”,并将它们替换为“IX”、“XC”和“CM”分别。

     module Roman 
    ( roman2Dec
     , dec2Roman
    ) where
    
    import Data.List (isInfixOf)
    
    -- The max number the program can deal with will not exceed than 4999
    
    romanUnits :: [(Char, Int)]
    romanUnits = [('I', 1), ('V', 5), ('X', 10), ('L', 50), ('C', 100), ('D', 500), ('M', 1000)]
    
    romanDividers :: [Int]
    romanDividers = reverse $ map snd romanUnits
    
    romanDigits :: [Char]
    romanDigits  = reverse $ map fst romanUnits
    
    -- short divide n by each of those in dividers
    shortDivide :: Int -> [Int] -> [Int]
    shortDivide n [] = []
    shortDivide n dividers = let (quotient, remainder) = n `divMod` (head dividers)
                             in quotient : shortDivide remainder (tail dividers)
    
    dec2Roman :: Int -> String
    dec2Roman n = concat $ map prettyRoman1 (zipWith (\x y -> replicate x y) (shortDivide n romanDividers) romanDigits)
    
    prettyRoman1 :: String -> String
    prettyRoman1 roman
      | roman == "IIII" = "IV"
      | roman == "XXXX" = "XL"
      | roman == "CCCC" = "CD"
      | otherwise = roman
    
    -- prettyRoman2: Replace VIV, LXL, DCD with IX, XC, and CM respectively. 
    -- After that, the dec2Roman will be modifed as dec2Roman' = prettyRoman2 dec2Roman
    prettyRoman2 :: String -> String
    prettyRoman2 = undefined
    
    findKey :: Eq a => a -> [(a, b)] -> b
    findKey key = snd . head . filter (\(k, v) -> k == key)
    
    romanValue :: Char -> Int
    romanValue c = findKey c romanUnits
    
    roman2Dec :: String -> Int
    roman2Dec [] = 0
    roman2Dec [x] = romanValue x
    roman2Dec (x:y:xs)
      | romanValue x < romanValue y  = (-1) * romanValue x  + roman2Dec (y:xs)
      | otherwise = romanValue x + roman2Dec (y:xs)
    
于 2016-08-02T15:52:19.207 回答
0

有点晚了,但为了记录,我已经将我的代码从JS移植到了 Haskell。我相信它是最有效的整数到罗马数字转换器之一。但是到目前为止,它只能达到 3999。

import qualified Data.Map.Lazy as M
import Data.Bool (bool)
import Data.List (unfoldr)

numerals :: M.Map Int Char
numerals = M.fromList [(0,'I'),(1,'V'),(2,'X'),(3,'L'),(4,'C'),(5,'D'),(6,'M')]

toDigits :: Int -> [Int]
toDigits = unfoldr (\x -> bool Nothing (Just (rem x 10, div x 10)) (x > 0))

getFromMap :: Int -> M.Map Int Char -> Char
getFromMap = M.findWithDefault '_'

getNumeral :: (Int,Int) -> String
getNumeral t | td == 0   = ""
             | td <  4   = replicate td $ getFromMap (2 * ti) numerals
             | td == 4   = getFromMap (2 * ti) numerals : [getFromMap (2 * ti + 1) numerals]
             | td <  9   = getFromMap (2 * ti + 1) numerals : replicate (td - 5) (getFromMap (2 * ti) numerals)
             | otherwise = getFromMap (2 * ti) numerals : [getFromMap (2 * ti + 2) numerals]
               where ti  = fst t -- indices
                     td  = snd t -- digits

dec2roman :: Int -> String
dec2roman = foldl (\r t -> getNumeral t ++ r) "" . zipWith (,) [0..] . toDigits

*Main> dec2roman 1453
"MCDLIII"
于 2017-10-12T23:10:07.413 回答