map
- 改变每个元素
fold
- 结合所有元素
scan
- 结合所有元素以保持“总”的运行 - 这就是您所需要的
将所有内容都保留为整数会更容易,直到最后:
type PosixOffset = Integer
您的字符串listOfTimes
可能是 unix 时间、增量或错误值。我们可以代表它,Maybe (Either PosixOffset Integer)
但这可能会让人讨厌。让我们自己动手:
data Time = Unix PosixOffset | Inc Integer | Error String deriving Show
这使我可以灵活地处理我们稍后处理错误的操作:使用 使程序崩溃error
,向用户显示Error
消息但以某种方式允许他们恢复,或者忽略错误的值。
让我们制作一个安全的版本来替换read :: String -> Integer
,它会返回Nothing
而不是崩溃。我们需要import Data.Char (isDigit)
readInteger :: String -> Maybe Integer
readInteger "" = Nothing
readInteger xs | all isDigit xs = Just (read xs)
| otherwise = Nothing
现在我们可以将它用于readTime
一些有用的Error
消息。
readTime :: String -> Time
readTime ('u':xs) = case readInteger xs of
Just i -> Unix i
Nothing -> Error $ "readTime: there should be an integer after the u, but I got: " ++ 'u':xs
readTime [] = Error "readTime: empty time"
readTime xs = case readInteger xs of
Just i -> Inc i
Nothing -> Error $ "readTime: " ++ xs ++ " is neither a unix time nor an increment."
计划是将我们的 Strings 列表转换为 pairs 列表,(PosixOffset,Integer)
最后一个已知PosixOffset
的来自 unix 时间,以及当前的增量。然后我们需要能够将这些对转换为UTCTime
toUTC :: (PosixOffset,Integer) -> UTCTime
toUTC (p,i) = psUTC (p+i)
现在我们需要知道如何将 s 的总和Time
与 next结合起来Time
。我们将保留最后一个 unix 时间以供参考。
addTime :: (PosixOffset,Integer) -> Time -> (PosixOffset,Integer)
addTime (oldunix,oldinc) time = case time of
Unix new -> (new,0) -- If there's a new unix time, replace and reset the inc to 0.
Inc inc -> (oldunix,inc) -- If there's a new increment, replace the old one.
Error msg -> error msg -- If there's an error, crash showing it.
或者你可以使用
addTimeTolerant :: (PosixOffset,Integer) -> Time -> (PosixOffset,Integer)
addTimeTolerant (oldunix,oldinc) time = case time of
Unix new -> (new,0) -- If there's a new unix time, replace and reset the inc to 0.
Inc inc -> (oldunix,inc) -- If there's a new increment, replace the old one.
Error msg -> (oldunix,oldinc) -- If there's an error, ignore it and keep the time the same.
现在我们可以把它粘在一起:把String
s 变成Time
s,然后用ning把它们组合(PosixOffset,Integer)
成对,然后把所有得到的对变成s。scan
addTime
UTCTime
runningTotal :: [String] -> [UTCTime]
runningTotal [] = []
runningTotal xss = let (t:ts) = map readTime xss in -- turn Strings to Times
case t of
Error msg -> error msg
Inc _ -> error "runningTotal: list must start with a unix time"
Unix po -> map toUTC $ scanl addTime (po,0) ts -- scan the list adding times,
-- starting with an initial unix time
-- then convert them all to UTC
或者如果你喜欢保持冷静并继续的方法addTimeTolerant
,你可以使用
isn't_UnixTime :: Time -> Bool
isn't_UnixTime (Unix _) = False
isn't_UnixTime _ = True
runningTotalTolerant :: [String] -> [UTCTime]
runningTotalTolerant xss =
let ts = dropWhile isn't_UnixTime (map readTime xss) in -- cheerily find the first unix time
if null ts then [] else -- if there wasn't one, there are no UTCTimes
let (Unix po) = head ts in -- grab the first time
map toUTC $ scanl addTimeTolerant (po,0) (tail ts) -- scan the list adding times,
-- starting with an initial unix time
-- then convert them all to UTC