commit f10b2c0f69fde472c9856dc8bda8695fda181c69 Author: Tissevert Date: Fri Nov 8 18:51:01 2019 +0100 First working version diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ebadcab --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +dist*/ +.ghc.environment.* diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..00150e5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Revision history for RomanNum + +## 0.1.0.0 -- 2019-11-08 + +* A first working version, exposing a constructor, sum and shortcut for conversion from String diff --git a/RomanNum.cabal b/RomanNum.cabal new file mode 100644 index 0000000..f9a2e0d --- /dev/null +++ b/RomanNum.cabal @@ -0,0 +1,26 @@ +cabal-version: 2.4 +-- Initial package description 'RomanNum.cabal' generated by 'cabal init'. +-- For further documentation, see http://haskell.org/cabal/users-guide/ + +name: RomanNum +version: 0.1.0.0 +-- synopsis: +-- description: +-- bug-reports: +-- license: +license-file: LICENSE +author: Tissevert +maintainer: tissevert+devel@marvid.fr +-- copyright: +-- category: +extra-source-files: CHANGELOG.md + +library + exposed-modules: Data.RomanNum + -- other-modules: + -- other-extensions: + build-depends: base ^>=4.11.1.0 + , containers + hs-source-dirs: src + default-language: Haskell2010 + ghc-options: -Wall diff --git a/Setup.hs b/Setup.hs new file mode 100644 index 0000000..9a994af --- /dev/null +++ b/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/src/Data/RomanNum.hs b/src/Data/RomanNum.hs new file mode 100644 index 0000000..e782317 --- /dev/null +++ b/src/Data/RomanNum.hs @@ -0,0 +1,61 @@ +module Data.RomanNum ( + RomanNum(..) + , nullus + , plus + , r + ) where + +import Data.Map (Map, (!)) +import qualified Data.Map as Map (fromList) + +newtype RomanNum = RomanNum Int + +data RomanChar = I | V | X | L | C | D | M deriving (Bounded, Eq, Ord, Enum, Show, Read) + +romanChars :: [RomanChar] +romanChars = [minBound .. maxBound] + +base :: [(Int, [Char])] +base = base' 1 [] . concat $ show <$> romanChars where + base' _ l "" = l + base' size l chars = + base' (size * 10) ((size, take 3 chars):l) (drop 2 chars) + +values :: Map RomanChar Int +values = Map.fromList $ + zip romanChars (concat $ iterate (fmap (*10)) [1, 5]) + +instance Show RomanNum where + show (RomanNum 0) = "nullus" + show (RomanNum n) = show' n base where + show' 0 _ = "" + show' _ [] = error "Numeral system not fine-grained enough" + show' k ((size, digits):others) + | k < size = show' k others + | otherwise = + let decDigit = k `div` size in + showDecDigit decDigit digits ++ show' (k `mod` size) others + showDecDigit _ [] = error "no digits for conversion" + showDecDigit k [digit] = k `times` digit + showDecDigit k digits + | k == 4 || (k == 9 && length digits > 2) = + (head digits) : [head (drop (if k > 5 then 2 else 1) digits)] + | k > 4 = head (drop 1 digits) : (showDecDigit (k-5) digits) + | otherwise = k `times` (head digits) + times count letter = take count $ repeat letter + +r :: String -> RomanNum +r "" = nullus +r "nullus" = nullus +r n@[_] = RomanNum (values ! (read n)) +r (digit:nextDigit:otherDigits) = + let (rChar, nextRChar) = (read [digit], read [nextDigit]) in + if rChar < nextRChar + then RomanNum (values ! nextRChar - values ! rChar) `plus` r otherDigits + else RomanNum (values ! rChar) `plus` r(nextDigit:otherDigits) + +nullus :: RomanNum +nullus = RomanNum 0 + +plus :: RomanNum -> RomanNum -> RomanNum +plus (RomanNum a) (RomanNum b) = RomanNum (a + b)