我有一个字符串列表,字符串可以是unixtime,也可以是unixtime的增量,例如。
listOfTimes :: [String]
listOfTimes = ["u1345469400","1","2","3","4","5","6","u1346427334","1","2","3","4","5","6"]
我编写的函数接受unixtime并返回UTCTime
dateStringToUTC :: [Char] -> UTCTime
dateStringToUTC a = out
where
asInt = read (tail a) :: Integer
out = psUTC asInt
或者采取增量和最后一次unixtime并返回UTCTime
incToUTC :: [Char] -> String -> UTCTime
incToUTC a b = madeDate
where
madeDate = psUTC posixOffset
posixOffset = lastTime + incTime
lastTime = read (tail a) :: Integer
incTime = read b :: Integer
但是我想不出一种方法来编写一个函数,我可以映射整个列表,返回一个[UTCTime]
答案 0 :(得分:4)
正如ja的回答所说,这不是一张简单的地图。一般折叠可以工作,但任何列表操作都是如此。
你在这里尝试做的事情听起来更像a use for scanr
,这是一个正确的折叠,它产生每个中间步骤的列表,而不仅仅是最终结果。在您的情况下,累加器将是上一个时间值,并且在每个步骤中,您要么添加增量,要么用新时间替换它。输出将是每个计算时间的(懒惰!)列表。
答案 1 :(得分:1)
它不是地图,因为你的inc函数有2个参数 - 你在后续调用中使用了前面的list元素。查看折叠:foldl
,foldr
等
答案 2 :(得分:1)
另一种方法是将相互对应的时间收集到一个单独的列表中,并单独处理它们,即
convertUTCs [] = []
convertUTCs (x:xs) = map (incToUTC x) increments ++ convertUTCs rest
where
(increments, rest) = break (\str -> head str == 'u') xs
这将获取第一个元素(应始终为"u12345"
形式)和该时间的所有增量(即不以'u'
开头的元素),然后执行对它们进行处理。
答案 3 :(得分:0)
timesToUnixTimes :: [String] -> [UTCTime]
正如ja所指出的,这不是一个简单的map
。但是,将[Integer]
转换为[UTCTime]
的最后一步是map
:
timesToUnixTimes (s : ss) = map psUTC (i : is)
where
输入列表的第一个元素s
最好是unixtime:
i = read (tail s) :: Integer
后续元素ss
也可以是,因此解码函数需要访问输出列表的前一个元素:
is = zipWith timeToInteger ss (i : is)
写作timeToInteger :: String -> Integer -> Integer
留作练习。
有两点:
您可以将zipWith
视为一次将一个函数映射到两个列表上(类似地,zipWith3
一次将一个函数映射到三个列表上,zipWith4
映射到一次有四个列表等;没有名为zipWith1
的函数,因为它被称为map
)。
is
出现在自己的定义中。这可以归功于 laziness 非严格性。
is
的第一个元素取决于ss
和i
的第一个元素。is
的第二个元素取决于ss
的第二个元素以及is
的第一个元素。is
的第三个元素取决于ss
的第三个元素和is
的第二个元素。 is
的元素不依赖于自身,也不依赖于is
的后期元素。
答案 4 :(得分:0)
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."
计划是将我们的字符串列表转换为成对列表(PosixOffset,Integer)
,
使用unix时间的最后一个PosixOffset
和当前增量。
然后,我们需要能够将这些对转换为UTCTime
toUTC :: (PosixOffset,Integer) -> UTCTime
toUTC (p,i) = psUTC (p+i)
现在我们需要知道如何将Time
的运行总和与下一个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
转换为Time
s,
然后通过(PosixOffset,Integer)
与scan
将它们组合成addTime
对,
然后将所有结果对转换为UTCTime
s。
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