使用Seq.map时如何获得之前的值?

时间:2016-01-06 17:40:06

标签: f#

我有一个日志文件,其行开头就是这样......

2016-01-06 16:06:52,000 [1]DEBUG ...
2016-01-06 16:06:52,100 [1]DEBUG ...
2016-01-06 16:06:52,030 [1]DEBUG ...

我想解析这个文件,并将从前一个日志行开始经过的时间添加到下一个日志的开头。例如,如果我有这样的行......

0 2016-01-06 16:06:52,000 [1]DEBUG ...
100 2016-01-06 16:06:52,100 [1]DEBUG ...
30 2016-01-06 16:06:52,130 [1]DEBUG ...

...然后我想制作以下内容......

let parseDate (s:string) =
  let time = ((s.Split [|' '|]) |> Seq.nth 1).Replace(",", ".")
  DateTime.Parse(time)

...每行的第一个数字是自上一行以来的毫秒数。

现在在C#中,我会使用一个(可变的)变量来保存上一次,然后从中减去新的时间,给我一个失效。但是,我认为变量是你应该在F#中避免的,所以想知道应该怎么做。

到目前为止,我有一个函数将文件读入一个序列,以及以下函数从一行中提取时间(我不担心这种情况下的日期,因为所有时间都是在同一天)...

let start = logLines |> Seq.head |> parseDate

let linesWithTimes = logLines |> Seq.map (fun l ->
  (((parseDate l) - start).TotalMilliseconds).ToString() + " "+ l
)

我可以执行以下操作,这会添加自第一个条目以来的总毫秒数...

{{1}}

如何添加自上次日志条目以来经过的时间?

希望很清楚。我是F#的新手,所以如果有更好的方法,请告诉我。

1 个答案:

答案 0 :(得分:11)

有很多方法,这里是使用Seq.pairwise的解决方案:

let linesWithTimes = 
    logLines 
        |> Seq.map (fun x -> parseDate x, x)
        |> Seq.pairwise
        |> Seq.map (fun ((dt1, x1), (dt2, x2)) -> string (dt2 - dt1).TotalMilliseconds + " " + x2)
        |> Seq.append (seq ["0 " + Seq.head logLines])

另一种进行映射但在元素之间组合值的方法是使用fold:

let linesWithTimes =
    logLines 
        |> Seq.fold (fun s t ->
            let dt = parseDate t
            match s with
            | None  , _   -> Some dt, ["0 " + t]
            | Some d, lst -> Some dt, lst @ [string (dt - d).TotalMilliseconds + " " + t]
                ) (None , [])
        |> snd
        |> List.toSeq

这是一个使用序列表达式的解决方案,它的时间更长并且它使用了一个可变的但在可枚举非常长且遍历速度慢的情况下可能更合适:

let linesWithTimes =
    let mutable prev = None : DateTime option
    seq {
        for e in logLines do
            let dt = parseDate e
            let diff = 
                match prev with
                | None   -> 0.
                | Some t -> (dt - t).TotalMilliseconds
            yield string diff + " " + e
            prev <- Some dt
         }