Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do i get the previous value when using Seq.map?

Tags:

f#

I have a log file whose lines start like this...

2016-01-06 16:06:52,778 [1]DEBUG ...blah blah blah

I want to parse this file and add the elapsed time from the previous log line onto the start of the next. For example, if I have lines like this...

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 ...

...then I would like to produce the following...

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 ...

...where the first number on each line is the number of milliseconds since the previous line.

Now in C#, I would use a (mutable) variable to hold the previous time, then subtract the new time from that, giving me the lapse. However, I think mutables are something you're supposed to avoid in F#, so was wondering how it should be done.

So far, I have a function that reads the file into a sequence, and the following function to extract the time from a single line (I'm not worried about the date in this case, as all timings will be on the same day)...

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

I can do the following, which adds the total number of milliseconds since the first entry...

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

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

How do I add the elapsed time since the previous log entry?

Hope that was clear. I'm new to F#, so if there's a better way to do it, please let me know.

like image 530
Avrohom Yisroel Avatar asked Sep 19 '25 23:09

Avrohom Yisroel


1 Answers

There are many ways, here's a solution using 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])

Another way to do a mapping but combining values between the elements is to use a 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

And here's a solution using sequence expressions, it's longer and it uses a mutable but it might be more appropriate in scenarios when the enumerable is very long and slow to traverse:

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
         }
like image 148
Gus Avatar answered Sep 23 '25 09:09

Gus