brainfrz icon

Haskell number-English converter

brainfrz | PRO | 11/03/17 06:22:34 AM UTC | 0 ⭐ | 12431 👁️ | Never ⏰ | []
Haskell |

5.82 KB

|

None

|

0 👍

/

0 👎

import Data.Char
import Data.List
import Data.Maybe
 
 
ones :: Integral a => [(a, String)]
ones = [(1,"one"), (2,"two"), (3,"three"), (4,"four"), (5,"five"), (6,"six"), (7,"seven"),
        (8,"eight"), (9,"nine")]
 
teens :: Integral a => [(a, String)]
teens = [(10,"ten"), (11,"eleven"), (12,"twelve"), (13,"thirteen"), (14,"fourteen"),
         (15,"fifteen"), (16,"sixteen"), (17,"seventeen"), (18,"eighteen"), (19,"nineteen")]
 
tens :: Integral a => [(a, String)]
tens = [(10,"ten"), (20,"twenty"), (30,"thirty"), (40,"forty"), (50,"fifty"), (60,"sixty"),
        (70,"seventy"), (80,"eighty"), (90,"ninety")]
 
groups :: [String]
groups = ["", " thousand", " million", " billion", " trillion", " quadrillion", " quintillion",
          " sextillion", " septillion", " octillion", " nonillion", " decillion", " undecillion",
          " duodecillion", " tredecillion", " quattuordecillion", " quindecillion", " sexdecillion",
          " septendecillion", " octodecillion", " novemdecillion", " vigintillion"]
 
-- Maps a word to its power of 10
groupList :: Integral a => [(String, a)]
groupList = [("thousand",3), ("million",6), ("billion",9), ("trillion",12), ("quadrillion",15),
             ("quintillion",18), ("sextillion",21), ("septillion",24), ("octillion",27),
             ("nonillion",30), ("decillion",33), ("undecillion",36), ("duodecillion",39),
             ("tredecillion",42), ("quattuordecillion",45), ("quindecillion",48),
             ("sexdecillion",51), ("septendecillion",54), ("octodecillion",57),
             ("novemdecillion",60), ("vigintillion",63)]
 
 
numToWord :: Integral a => a -> String
numToWord n
    | n < 0      = "negative " ++ numToWord (-n)
    | n == 0     = "zero"
    | n >= 10^65 = error "Doesn't support numbers bigger than vigintillions! (10^65-1)"
    | isHundred  = groupToWord d100 ++ " hundred"
    | otherwise  = unwords $ reverse buildGroups
    where
        buildGroups = map (uncurry (++)) (filter (\(n,g) -> n /= "") (zip wordGroups groups))
        wordGroups = map groupToWord $ splitNum n
 
        isHundred = n <= 9000 && r1000 /= 0 && r100 == 0
        r1000 = n `rem` 1000
        (d100,r100) = n `quotRem` 100
 
        toWord :: Integral a => a -> [(a, String)] -> String
        toWord n table = fromMaybe "" (lookup n table)
 
        groupToWord :: Integral a => a -> String
        groupToWord n
            | n < 0     = "negative " ++ groupToWord (-n)
            | n == 0    = ""
            | otherwise = unwords $ numWords n
            where
                numWords :: Integral a => a -> [String]
                numWords r
                    | r <= 0               = []
                    | r < 10               = [toWord r ones]
                    | r < 20               = [toWord r teens]
                    | r < 100 && r10 /= 0  = [toWord n10 tens ++ "-" ++ toWord r10 ones]
                    | r < 100              = toWord n10 tens : numWords r10
                    | r < 1000             = toWord d100 ones : "hundred" : numWords r100
                    | otherwise            = error "groupToWord: not a 3-digit group"
                    where
                        (n10, r10) = (r - r10, r `rem` 10)
                        (d100, r100) = r `quotRem` 100
 
        -- Splits a number into groups in reverse order
        splitNum :: Integral a => a -> [a]
        splitNum n
            | d == 0  = [n]
            | otherwise = m : splitNum d
            where
                (d,m) = n `quotRem` 1000
 
 
 
wordToNum :: Integral a => String -> a
wordToNum s
    | s == ""    = 0
    | isNegative = (-1) * (wordToNum $ unwords $ tail (words s))
    | otherwise  = currBlock + nextBlock
    where
        isNegative = head (words s) == "negative" || head (words s) == "minus"
        (b, bs) = span (\w -> not $ groupWord w) (words s)
 
        currBlock
            | bs == []    = blockToNum $ unwords b
            | otherwise   = (fromGroup $ head bs) * (blockToNum $ unwords b)
        nextBlock
            | bs == []        = 0
            | length bs == 1  = 0
            | otherwise       = wordToNum (unwords $ tail bs)
 
        fromWord :: Integral a => String -> [(a, String)] -> a
        fromWord word table = key
            where (key,val) = head $ dropWhile (\(n,w) -> w /= word) table
 
        -- Calculates the group's multiplier
        fromGroup :: Integral a => String -> a
        fromGroup g = 10 ^ (fromMaybe 0 (lookup g groupList))
 
        clean :: String -> String
        clean "" = ""
        clean ('-':cs) = ' ' : clean cs
        clean (c:cs)
            | isLetter c  = c : clean cs
            | otherwise   = clean cs
 
        mapMember :: Integral a => String -> [(a,String)] -> Bool
        mapMember _ [] = False
        mapMember w ((_,s):ws) = w == s || mapMember w ws
 
        groupWord :: String -> Bool
        groupWord s = inGroupList s groupList
          where
            inGroupList :: Integral a => String -> [(String,a)] -> Bool
            inGroupList _ [] = False
            inGroupList w ((s,_):ws) = w == s || inGroupList w ws   
 
        blockToNum :: Integral a => String -> a
        blockToNum s = block (reverse (map clean (words s)))
 
        block :: Integral a => [String] -> a
        block [] = 0
        block [""] = 0
        block (x:y:z:[]) = (100 * (fromWord z ones)) + (block $ words x)  -- must be x hundred z
        block (x:y:[])
            | x == "hundred" = 100 * (block $ words y) -- Must recurse for x hundred cases (e.g. seventeen hundred)
            | otherwise      = (fromWord x tens) + (fromWord y ones)
        block [w]
            | length (words w) == 2  = block $ words w
            | mapMember w ones       = fromWord w ones
            | mapMember w teens      = fromWord w teens
            | mapMember w tens       = fromWord w tens

Comments