Created
May 24, 2013 13:39
-
-
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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(* 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 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
You don't need the ';' to separate the lists items if they are on separate lines.