· f

F#: Convert sequence to comma separated string

I’ve been continuing playing around with parsing Cruise data as I mentioned yesterday with the goal today being to create a graph from the build data.

After recommendations from Dean Cornish and Sam Newman on Twitter I decided to give the Google Graph API a try to do this and realised that I would need to create a comma separated string listing all the build times to pass to the Google API.

My initial thinking was that I could just pipe the sequence of values through 'Seq.fold' and add a comma after each value:

let ConvertToCommaSeparatedString (value:seq<string>) =
    let initialAttempt = value |> Seq.fold (fun acc x -> acc + x + ",") ""
    initialAttempt.Remove(initialAttempt.Length-1)

It works but you end up with a comma after the last value as well and then need to remove that on the next line which feels very imperative to me.

My next thought was that maybe I would be able to do this by making use of a recursive function which matched the sequence on each iteration and then when it was on the last value in the list to not add the comma.

I know how to do this for a list so I decided to go with that first:

let ConvertToCommaSeparatedString (value:seq<string>) =
    let rec convert (innerVal:List<string>) acc =
        match innerVal with
            | [] -> acc
            | hd::[] -> convert [] (acc + hd)
            | hd::tl -> convert tl (acc + hd + ",")
    convert (Seq.to_list value) ""

That works as well but it seems a bit weird that we need to convert everything in a list to do it.

A bit of googling revealed an interesting post by Brian McNamara where he suggests creating an active pattern which would cast the 'seq' to a 'LazyList' (which is deprecated but won’t be removed apparently) and then do some pattern matching against that instead.

The active pattern which Brian describes is like this:

let rec (|SeqCons|SeqNil|) (s:seq<'a>) =
    match s with
    | :? LazyList<'a> as l ->
        match l with
        | LazyList.Cons(a,b) -> SeqCons(a,(b :> seq<_>))
        | LazyList.Nil -> SeqNil
    | _ -> (|SeqCons|SeqNil|) (LazyList.of_seq s :> seq<_>)

This doesn’t cover the three states of the sequence which I want to match so I adjusted it slightly to do what I want:

let rec (|SeqCons|SeqNil|SeqConsLastElement|) (s:seq<'a>) =
    match s with
    | :? LazyList<'a> as l ->
        match l with
        | LazyList.Cons(a,b) ->
            match b with
                | LazyList.Nil -> SeqConsLastElement(a)
                | LazyList.Cons(_,_) -> SeqCons(a,(b :> seq<_>))
        | LazyList.Nil -> SeqNil
    | _ -> (|SeqCons|SeqNil|SeqConsLastElement|) (LazyList.of_seq s :> seq<_>)

Our function to convert sequences to a comma separated string would now look like this:

let ConvertToCommaSeparatedString (value:seq<string>) =
    let rec convert (innerVal:seq<string>) acc =
        match innerVal with
            | SeqNil -> acc
            | SeqConsLastElement(hd) -> convert [] (acc + hd)
            | SeqCons(hd,tl) -> convert tl (acc + hd + ",")
    convert (value) ""

An example of this in action would be like this:

ConvertToCommaSeparatedString (seq { yield "mark"; yield "needham" });;
val it : string = "mark,needham"
  • LinkedIn
  • Tumblr
  • Reddit
  • Google+
  • Pinterest
  • Pocket