How to resolve the algorithm Commatizing numbers step by step in the Haskell programming language
How to resolve the algorithm Commatizing numbers step by step in the Haskell programming language
Table of Contents
Problem Statement
Commatizing numbers (as used here, is a handy expedient made-up word) is the act of adding commas to a number (or string), or to the numeric part of a larger string.
Write a function that takes a string as an argument with optional arguments or parameters (the format of parameters/options is left to the programmer) that in general, adds commas (or some other characters, including blanks or tabs) to the first numeric part of a string (if it's suitable for commatizing as per the rules below), and returns that newly commatized string. Some of the commatizing rules (specified below) are arbitrary, but they'll be a part of this task requirements, if only to make the results consistent amongst national preferences and other disciplines. The number may be part of a larger (non-numeric) string such as:
The string may possibly not have a number suitable for commatizing, so it should be untouched and no error generated. If any argument (option) is invalid, nothing is changed and no error need be generated (quiet execution, no fail execution). Error message generation is optional. The exponent part of a number is never commatized. The following string isn't suitable for commatizing: 9.7e+12000 Leading zeroes are never commatized. The string 0000000005714.882 after commatization is: 0000000005,714.882 Any period (.) in a number is assumed to be a decimal point. The original string is never changed except by the addition of commas [or whatever character(s) is/are used for insertion], if at all. To wit, the following should be preserved:
Any exponent character(s) should be supported: Numbers may be terminated with any non-digit character, including subscripts and/or superscript: 41421356243 or 7320509076(base 24). The character(s) to be used for the comma can be specified, and may contain blanks, tabs, and other whitespace characters, as well as multiple characters. The default is the comma (,) character. The period length can be specified (sometimes referred to as "thousands" or "thousands separators"). The period length can be defined as the length (or number) of the decimal digits between commas. The default period length is 3. The location of where to start the scanning for the target field (the numeric part) should be able to be specified. The default is 1. The character strings below may be placed in a file (and read) or stored as simple strings within the program.
The value of pi (expressed in base 10) should be separated with blanks every 5 places past the decimal point, the Zimbabwe dollar amount should use a decimal point for the "comma" separator: where the penultimate string has three leading blanks (real blanks are to be used).
Let's start with the solution:
Step by Step solution about How to resolve the algorithm Commatizing numbers step by step in the Haskell programming language
The program commatize comma-separates numbers in a string, optionally with a different separator and grouping size.
The chopUp function chops a string into evenly sized pieces. This is used for the grouping of digits into groups for the commatization.
chopUp :: Int -> String -> [String]
chopUp _ [] = []
chopUp by str
| by < 1 = [str] -- invalid argument, leave string unchanged
| otherwise = let (pfx, sfx) = splitAt by str
in pfx : chopUp by sfx
The addSeps function adds a separator sep between groups of digits in a string. The rev function reverses a string, so the sep is added from the end of the string toward the beginning.
addSeps :: String -> Char -> Int -> (String -> String) -> String
addSeps str sep by rev =
let (leading, number) = span (== '0') str
number2 = rev $ intercalate [sep] $ chopUp by $ rev number
in leading ++ number2
The processNumber function commatizes a number. It deals with the special case of a decimal point and adds a comma every by digits.
processNumber :: String -> Char -> Int -> String
processNumber str sep by =
let (beforeDecimal, rest) = span isDigit str
(decimal, afterDecimal) = splitAt 1 rest
beforeDecimal2 = addSeps beforeDecimal sep by reverse
afterDecimal2 = addSeps afterDecimal sep by id
in beforeDecimal2 ++ decimal ++ afterDecimal2
The commatize2 function commatizes a string. It calls the processNumber function on the numeric part of the string.
commatize2 :: String -> Char -> Int -> String
commatize2 [] _ _ = []
commatize2 str sep by =
let (pfx, sfx) = break isDigitOrPeriod str
(number, sfx2) = span isDigitOrPeriod sfx
in pfx ++ processNumber number sep by ++ sfx2
The commatize function takes a string, an optional separator, and an optional grouping size and returns the commatized string.
commatize :: String -> Maybe Char -> Maybe Int -> String
commatize str sep by = commatize2 str (fromMaybe ',' sep) (fromMaybe 3 by)
Source code in the haskell programming language
#!/usr/bin/env runhaskell
import Control.Monad (forM_)
import Data.Char (isDigit)
import Data.List (intercalate)
import Data.Maybe (fromMaybe)
{-
I use the suffix "2" in identifiers in place of the more conventional
prime (single quote character), because Rosetta Code's syntax highlighter
still doesn't handle primes in identifiers correctly.
-}
isDigitOrPeriod :: Char -> Bool
isDigitOrPeriod '.' = True
isDigitOrPeriod c = isDigit c
chopUp :: Int -> String -> [String]
chopUp _ [] = []
chopUp by str
| by < 1 = [str] -- invalid argument, leave string unchanged
| otherwise = let (pfx, sfx) = splitAt by str
in pfx : chopUp by sfx
addSeps :: String -> Char -> Int -> (String -> String) -> String
addSeps str sep by rev =
let (leading, number) = span (== '0') str
number2 = rev $ intercalate [sep] $ chopUp by $ rev number
in leading ++ number2
processNumber :: String -> Char -> Int -> String
processNumber str sep by =
let (beforeDecimal, rest) = span isDigit str
(decimal, afterDecimal) = splitAt 1 rest
beforeDecimal2 = addSeps beforeDecimal sep by reverse
afterDecimal2 = addSeps afterDecimal sep by id
in beforeDecimal2 ++ decimal ++ afterDecimal2
commatize2 :: String -> Char -> Int -> String
commatize2 [] _ _ = []
commatize2 str sep by =
let (pfx, sfx) = break isDigitOrPeriod str
(number, sfx2) = span isDigitOrPeriod sfx
in pfx ++ processNumber number sep by ++ sfx2
commatize :: String -> Maybe Char -> Maybe Int -> String
commatize str sep by = commatize2 str (fromMaybe ',' sep) (fromMaybe 3 by)
input :: [(String, Maybe Char, Maybe Int)]
input =
[ ("pi=3.14159265358979323846264338327950288419716939937510582097494459231", Just ' ', Just 5)
, ("The author has two Z$100000000000000 Zimbabwe notes (100 trillion).", Just '.', Nothing)
, ("\"-in Aus$+1411.8millions\"", Nothing, Nothing)
, ("===US$0017440 millions=== (in 2000 dollars)", Nothing, Nothing)
, ("123.e8000 is pretty big.", Nothing, Nothing)
, ("The land area of the earth is 57268900(29% of the surface) square miles.", Nothing, Nothing)
, ("Ain't no numbers in this here words, nohow, no way, Jose.", Nothing, Nothing)
, ("James was never known as 0000000007", Nothing, Nothing)
, ("Arthur Eddington wrote: I believe there are 15747724136275002577605653961181555468044717914527116709366231425076185631031296 protons in the universe.", Nothing, Nothing)
, (" $-140000±100 millions.", Nothing, Nothing)
, ("6/9/1946 was a good year for some.", Nothing, Nothing)
]
main :: IO ()
main =
forM_ input $ \(str, by, sep) -> do
putStrLn str
putStrLn $ commatize str by sep
putStrLn ""
You may also check:How to resolve the algorithm Hello world/Newline omission step by step in the REBOL programming language
You may also check:How to resolve the algorithm Loops/Nested step by step in the Go programming language
You may also check:How to resolve the algorithm CSV data manipulation step by step in the Icon and Unicon programming language
You may also check:How to resolve the algorithm Loops/Infinite step by step in the Stata programming language
You may also check:How to resolve the algorithm Count occurrences of a substring step by step in the Raku programming language