2

我有一个字符串列表,这些字符串要么是 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] 的整个列表的函数

4

5 回答 5

4

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

您在此处尝试执行的操作听起来更具体地类似于使用 for scanr,这是一个正确的折叠,可以生成每个中间步骤的列表,而不仅仅是最终结果。在您的情况下,累加器将是以前的时间值,并且在每一步中,您要么添加增量,要么将其替换为新时间。输出将是每个计算时间的(懒惰!)列表。

于 2012-08-31T16:47:36.027 回答
1

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

于 2012-08-31T16:06:47.647 回答
1

另一种方法是将彼此对应的时间收集到一个单独的列表中,并分别处理,即

convertUTCs [] = []
convertUTCs (x:xs) = map (incToUTC x) increments ++ convertUTCs rest
  where
    (increments, rest) = break (\str -> head str == 'u') xs

这需要第一个元素(应始终为 形式"u12345")和该时间的所有增量(即不以 开头的元素'u'),然后对它们进行处理。

于 2012-08-31T18:28:18.147 回答
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."

计划是将我们的 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.

现在我们可以把它粘在一起:把Strings 变成Times,然后用ning把它们组合(PosixOffset,Integer)成对,然后把所有得到的对变成s。scanaddTimeUTCTime

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
于 2012-09-01T15:59:47.780 回答
0
timesToUnixTimes :: [String] -> [UTCTime]

正如 ja 指出的那样,这不是一个简单的map. [Integer]但是将 a 转换为 a的最后一步[UTCTime]是 a 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出现在它自己的定义中。这要归功于惰性非严格性。

    1. 的第一个元素is取决于 和 的第一个ss元素i
    2. 的第二个元素is取决于 的第二个元素ss和 的第一个元素is
    3. 的第三个元素is取决于 的第三个元素ss和 的第二个元素is
    4. 等等。

    的任何元素都不is依赖于自身,或依赖于 的后一个元素is

于 2012-08-31T16:50:02.203 回答