This is an ordinary data processing task: read a list of dates and amounts (for example, deposits and withdrawals from a bank account) and report the date on which the lowest balance was recorded and what that balance was. While I do get the correct answer--always a virtue--with the code below, I can't help thinking that it could be done better (e.g. more concisely). I've included the preliminary input parsing code so that the whole thing can be compiled and run as is.
module Main where
import Text.ParserCombinators.Parsec
import Data.Time
import Numeric
import Control.Applicative ((<$>), (<*>), (*>))
import Control.Monad.State as CM
--The purpose is to sum up a list of dated values (example: transactions in a bank
--account) and produce the date and amount of the lowest recorded balance.
--For the three lines below, the output would be:
-- DtdBal {runningBal = 4859.96, lowDt = 2013-01-23, lowBal = 4859.96}
--12/1/12, 6844.79
--1/1/13, 992.41
--1/23/13, -2977.24
data DtdVal = DtdVal { dt :: Day
, amt :: Double
} deriving Show
data DtdBal = DtdBal { runningBal :: Double
, lowDt :: Day
, lowBal :: Double
} deriving Show
main = do res <- parseFromFile sched "escrow.txt"
case res of
Right l -> putStrLn . show $ process l
Left _ -> error "Oops"
where process (x:xs) = execState (minBal xs) (initState x)
initState x = DtdBal { runningBal = amt x, lowDt = dt x, lowBal = amt x }
minBal :: [DtdVal] -> CM.State DtdBal ()
minBal [] = return ()
minBal (x:xs) = do
rBal <- runningBal <$> get
lBal <- lowBal <$> get
lDt <- lowDt <$> get
if amt x + rBal < lBal
then put $ DtdBal { runningBal = rBal + amt x, lowDt = dt x, lowBal = amt x + rBal}
else put $ DtdBal { runningBal = rBal + amt x, lowDt = lDt, lowBal = lBal }
minBal xs
sched :: CharParser () [DtdVal]
sched = line `endBy` newline
line :: CharParser () DtdVal
line = DtdVal <$> dts <*> (char ',' >> spaces *> readSgnd)
dts :: CharParser () Day
dts = toDay <$> many digit `sepBy` char '/'
where toDay [x, y, z] = fromGregorian (2000 + read z) (read x) (read y)
readSgnd :: CharParser () Double
readSgnd = do
optional (char '+')
inp <- getInput
let [(num, inp')] = readSigned readFloat inp
setInput inp'
return num