稍微复杂[String] - > [UTCTime]

时间:2012-08-31 15:59:31

标签: haskell

我有一个字符串列表,字符串可以是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]

5 个答案:

答案 0 :(得分:4)

正如ja的回答所说,这不是一张简单的地图。一般折叠可以工作,但任何列表操作都是如此。

你在这里尝试做的事情听起来更像a use for scanr,这是一个正确的折叠,它产生每个中间步骤的列表,而不仅仅是最终结果。在您的情况下,累加器将是上一个时间值,并且在每个步骤中,您要么添加增量,要么用新时间替换它。输出将是每个计算时间的(懒惰!)列表。

答案 1 :(得分:1)

它不是地图,因为你的inc函数有2个参数 - 你在后续调用中使用了前面的list元素。查看折叠:foldlfoldr

答案 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留作练习。

有两点:

  1. 您可以将zipWith视为一次将一个函数映射到两个列表上(类似地,zipWith3一次将一个函数映射到三个列表上,zipWith4映射到一次有四个列表等;没有名为zipWith1的函数,因为它被称为map)。

  2. is出现在自己的定义中。这可以归功于 laziness 非严格性。

    1. is的第一个元素取决于ssi的第一个元素。
    2. is的第二个元素取决于ss的第二个元素以及is的第一个元素。
    3. is的第三个元素取决于ss的第三个元素和is的第二个元素。
    4. 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