6
\$\begingroup\$

I've implemented 12 Days of Christmas just by using pattern matching. It's not very efficient as it performs 12 iterations, each performing its own iteration (potentially from 12 down to 1... does tail recursion kick in here though?) but I supposed I can live with that at the moment. I'm more concerned with learning how idiomatic F# is written. Would an F# programmer be more concerned with creating types, etc.?

module Christmas
let getOrdinal num =
    match num with
    | 1 -> sprintf "%ist" num
    | 2 -> sprintf "%ind" num
    | 3 -> sprintf "%ird" num
    | _ -> sprintf "%ith" num

let getPreamble num =
    sprintf "On the %s day of Christmas, my true love gave to me" (getOrdinal num)

let rec getVerse verse num = 
    match num with
    | 12 -> getVerse (List.append verse ["12 drummers drumming"]) (num - 1)
    | 11 -> getVerse (List.append verse ["11 pipers piping"]) (num - 1)
    | 10 -> getVerse (List.append verse ["10 lords a-leaping"]) (num - 1)
    | 9 -> getVerse (List.append verse ["9 ladies dancing"]) (num - 1)
    | 8 -> getVerse (List.append verse ["8 maids a-milking"]) (num - 1)
    | 7 -> getVerse (List.append verse ["7 swans a-swimming"]) (num - 1)
    | 6 -> getVerse (List.append verse ["6 geese a-laying"]) (num - 1)
    | 5 -> getVerse (List.append verse ["5 golden rings"]) (num - 1)
    | 4 -> getVerse (List.append verse ["4 calling birds"]) (num - 1)
    | 3 -> getVerse (List.append verse ["3 French hens"]) (num - 1)
    | 2 -> getVerse (List.append verse ["2 turtle doves, and"]) (num - 1)
    | _ -> List.append verse ["A partridge in a pear tree"]

let composeVerse num =
    List.append [getPreamble num] (getVerse [] num)
    |> List.fold (fun r s -> r + s + "\n") ""


[1 .. 12]
|> Seq.iter (fun a -> printfn "%s" (composeVerse a))
\$\endgroup\$

2 Answers 2

3
\$\begingroup\$

You should generally avoid List.append because appending to a singly-linked list is quite inefficient.

I think your getVerse function is probably doing too much. You can have a function to get a line at a time which is just int -> string

Recursion can be avoided here if you switch to a list comprehension (the for...in syntax). Recursion is rarely needed in F#.

Even folding can be avoided here as it's simpler to use String.concat.

With all of these changes and a couple more:

let ordinalSuffix = function 1 -> "st" | 2 -> "nd" | 3 -> "rd" | _ -> "th"

let preamble num =
    sprintf "On the %i%s day of Christmas, my true love gave to me" num (ordinalSuffix num)

let line num = 
    match num with
    | 12 -> "12 drummers drumming"
    | 11 -> "11 pipers piping"
    | 10 -> "10 lords a-leaping"
    | 9 -> "9 ladies dancing"
    | 8 -> "8 maids a-milking"
    | 7 -> "7 swans a-swimming"
    | 6 -> "6 geese a-laying"
    | 5 -> "5 golden rings"
    | 4 -> "4 calling birds"
    | 3 -> "3 French hens"
    | 2 -> "2 turtle doves, and"
    | _ -> "A partridge in a pear tree"

let verse num =
    preamble num :: [ for i in num .. -1 .. 1 -> line i ]
    |> String.concat "\n"

[1 .. 12] |> List.map verse |> String.concat "\n\n" |> printfn "%s"
\$\endgroup\$
1
  • \$\begingroup\$ Great point on appending to a singly-linked list. I was completely unaware that F# devs avoid recursion. Thank you for reminding me of the cons operator in the verse function. Great feedback! Thank you! \$\endgroup\$ Commented Mar 26, 2018 at 15:16
2
\$\begingroup\$

I think TheQuickBownFox's suggestion is rather elegant. With a little twist (the use of List.mapFold) it is only necessary to call line num once per num:

module Christmas =

    let getOrdinal num =
        match num with
        | 1 -> sprintf "%ist" num
        | 2 -> sprintf "%ind" num
        | 3 -> sprintf "%ird" num
        | _ -> sprintf "%ith" num

    let getPreamble num =
        sprintf "On the %s day of Christmas, my true love gave to me" (getOrdinal num)


    let getVerseLine num = 
        match num with
        | 12 -> "12 drummers drumming"
        | 11 -> "11 pipers piping"
        | 10 -> "10 lords a-leaping"
        | 9 -> "9 ladies dancing"
        | 8 -> "8 maids a-milking"
        | 7 -> "7 swans a-swimming"
        | 6 -> "6 geese a-laying"
        | 5 -> "5 golden rings"
        | 4 -> "4 calling birds"
        | 3 -> "3 French hens"
        | 2 -> "2 turtle doves, and"
        | _ -> "A partridge in a pear tree"

    let composeVerse num verseLines =
        let verse = (getVerseLine num)::verseLines
        (getPreamble num)::verse, verse

    let getVerses= 
        fst ([1 .. 12] |> List.mapFold (fun verseLines num -> composeVerse num verseLines) [])
\$\endgroup\$
1
  • 1
    \$\begingroup\$ mapFold That's actually a new one for me. Super cool suggestion. \$\endgroup\$ Commented Mar 26, 2018 at 15:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.