Skip to content

Instantly share code, notes, and snippets.

@dtchepak
Created May 24, 2013 13:39
Show Gist options
  • Save dtchepak/5643572 to your computer and use it in GitHub Desktop.
Save dtchepak/5643572 to your computer and use it in GitHub Desktop.
Attempt at Survey Csv exercise in F#. https://gist.github.com/dtchepak/5640717
(* This attempt at the problem (https://gist.github.com/dtchepak/5640717) was
based on some real code I had to write.
The aim of this particular approach was to have a deterministic column order,
plus ensure headers and data stayed in the correct order. I ended up with a
DSL-sort-of-thing to define columns:
* `col` to make a column with a particular header and selector; and
* `lookupCol` to make a header and selector to lookup a result for a language.
This is probably unecessary for this case, but seemed appropriate for the real
case which had more columns and column types, so I thought I'd try it out here too.
*)
module SurveyCsv
open FSharp
open FSharpx
type ProgrammingLanguage =
| CSharp
| FSharp
| Haskell
| Ruby
| JavaScript
override x.ToString() = sprintf "%A" x
type SurveyResponse =
{ site : string
; results : Map<ProgrammingLanguage, int>
}
let private lower (s:string) = s.ToLower()
let reportColumns =
let findOrZero x = Option.getOrElse 0 << Map.tryFind x
let col name selector = (name, selector)
let lookupCol lang = (string lang |> lower, fun response -> response.results |> findOrZero lang |> string)
[ col "site" (fun x -> x.site)
; lookupCol CSharp
; lookupCol FSharp
; lookupCol Haskell
; lookupCol Ruby
; lookupCol JavaScript
]
let surveyToCsv (surveys : SurveyResponse seq) : string =
let appendLine this that = this + "\n" + that
let headers = reportColumns |> Seq.map fst |> String.concat ","
let columnValues response = reportColumns |> Seq.map (fun x -> snd x response) |> String.concat ","
surveys
|> Seq.map (columnValues)
|> String.concat "\n"
|> appendLine headers
// Tests
open NUnit.Framework
open FsUnit
[<Test>]
let testExample() =
let responses = [ {site="site1"; results=[CSharp, 3; FSharp, 1; Haskell, 0] |> Map.ofList }
; {site="site2"; results=[CSharp, 3; Ruby, 5] |> Map.ofList }
; {site="site3"; results=[Ruby, 7; JavaScript, 4] |> Map.ofList }
]
let expected = "site,csharp,fsharp,haskell,ruby,javascript\n" +
"site1,3,1,0,0,0\n" +
"site2,3,0,0,5,0\n" +
"site3,0,0,0,7,4"
surveyToCsv responses |> should equal expected
open Microsoft.FSharp.Reflection
let getAllUnionCaseValues<'T>() : 'T seq =
FSharpType.GetUnionCases(typeof<'T>)
|> Seq.map (fun x -> unbox (FSharpValue.MakeUnion(x, [| |])))
[<Test>]
let ``all languages should appear in report header``() =
let allLangs = getAllUnionCaseValues<ProgrammingLanguage>() |> Seq.map string |> String.concat "," |> lower
surveyToCsv Seq.empty |> should contain allLangs
@7sharp9
Copy link

7sharp9 commented May 24, 2013

You don't need the ';' to separate the lists items if they are on separate lines.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment