Project Euler - Poker Hands (2) - F#

In previous post I showed a C# solution to the Poker-hands problem. Since I’ve become more and more interested in functional programming languages, I would like to create another solution in F# and give a comparison to the C# one.

##Solution

open System
open System.IO

type Player = | Player1 | Player2

let cards = ['0';'1';'2'; '3'; '4'; '5'; '6'; '7'; '8'; '9'; 'T'; 'J'; 'Q'; 'K'; 'A']
let royal = cards |> List.skip 10 |> Array.ofList
let cardOrder c = cards |> List.findIndex ((=) c)
let highToLow = cardOrder >> (-) 15

let isInfix (hand: char array) =
  let cardss = cards |> Array.ofList |> String

  hand
  |> String
  |> fun s -> cardss.Contains(s)
  |> (&&) (hand.Length = 5)

let handValue str =
  let freq      = Array.groupBy fst >> Array.map (fun (n, v) -> (n, v.Length)) >> Array.sortBy (fun (c, l) -> 100 * (5-l) + highToLow c)
  let hand      = str    |> Array.map (fun (s:string) -> (s.[0], s.[1]))
  let values    = hand   |> Array.map fst |> Array.sortBy cardOrder
  let valuesH2L = values |> Array.rev |> String
  let suits     = hand   |> Array.map snd

  let isFlush    = suits  |> Array.distinct |> Array.length |> (=) 1 // same suit
  let isStraight = values |> isInfix // consecutive
  let isRoyal    = values = royal

  let expFreq e a = a |> Array.map snd |> function | v when v = e -> a |> Array.map fst |> String |> Some | _ -> None

  let (|FourOfAKind|_|)  = expFreq [|4;1|]
  let (|ThreeOfAKind|_|) = expFreq [|3;1;1|]
  let (|FullHouse|_|)    = expFreq [|3;2|]
  let (|TwoPairs|_|)     = expFreq [|2;2;1|]
  let (|OnePair|_|)      = expFreq [|2;1;1;1|]

  match freq hand with
  | _ when isFlush && isRoyal     -> "900000"
  | _ when isFlush && isStraight  -> "8" + valuesH2L
  | FourOfAKind s                 -> "7000" + s
  | FullHouse   s                 -> "6000" + s
  | _ when isFlush                -> "5" + valuesH2L
  | _ when isStraight             -> "4" + valuesH2L
  | ThreeOfAKind s                -> "300" + s
  | TwoPairs s                    -> "200" + s
  | OnePair s                     -> "10" + s
  | _ -> "0" + valuesH2L


let playHand line =
  let h1 = line |> Array.take 5 |> handValue |> List.ofSeq
  let h2 = line |> Array.skip 5 |> handValue |> List.ofSeq

  let rec winner = function
  | ((a, b)::xs) when cardOrder a > cardOrder b -> Player1
  | ((a, b)::xs) when cardOrder a = cardOrder b -> winner xs
  | _ -> Player2

  List.zip h1 h2 |> winner

File.ReadAllLines(@"p054_poker.txt")
|> Seq.map (fun s -> s.Split ' ' |> playHand)
|> Seq.filter ((=) Player1)
|> Seq.length
|> printf "Player 1 won %d"

##Comparison

This implemetaion has only 66 lines of code which is much lesser than its C# conterpart. In C# soultion, I defined an interface, an abstract base class to abstract and encapsulate game rules. The business logic is hidding in each sub-class with the overhead of inheritance.

 ....
 public class Full_House: Rank {
    public Full_House(IEnumerable<string> cards) : base(cards) {

    }
    protected override bool Match() {

        return cards.GroupBy(c => Mapper[c[0]], c=> 1).Count(g => g.Count()==3) == 1 
            && cards.GroupBy(c => Mapper[c[0]], c=> 1).Count(g => g.Count()==2) == 1;
    }

   protected override double Remainder { 
       get{
         return GetGroups(3).First().Key * 15 + GetGroups(2).First().Key;
       }
   }
}   
...

In contrast, F#’s Discriminated unions enables a concise coding style when working with hierarchical data. Buisness logic is a collection of partially applied functions. This enables data to be processed streamly and results in an expressive and succinct code.

match freq hand with
| _ when isFlush && isRoyal     -> "900000"
| _ when isFlush && isStraight  -> "8" + valuesH2L
| FourOfAKind s                 -> "7000" + s
| FullHouse   s                 -> "6000" + s
| _ when isFlush                -> "5" + valuesH2L
| _ when isStraight             -> "4" + valuesH2L
| ThreeOfAKind s                -> "300" + s
| TwoPairs s                    -> "200" + s
| OnePair s                     -> "10" + s
| _ -> "0" + valuesH2L