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"
About the author
I'm currently working on short form content at ClickHouse. I publish short 5 minute videos showing how to solve data problems on YouTube @LearnDataWithMark. I previously worked on graph analytics at Neo4j, where I also co-authored the O'Reilly Graph Algorithms Book with Amy Hodler.