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