Project Euler – Problem 54 Solution

You can become a serverless blackbelt. Enrol to my 4-week online workshop Production-Ready Serverless and gain hands-on experience building something from scratch using serverless technologies. At the end of the workshop, you should have a broader view of the challenges you will face as your serverless architecture matures and expands. You should also have a firm grasp on when serverless is a good fit for your system as well as common pitfalls you need to avoid. Sign up now and get 15% discount with the code yanprs15!


In the card game poker, a hand consists of five cards and are ranked, from lowest to highest, in the following way:

  • High Card: Highest value card.
  • One Pair: Two cards of the same value.
  • Two Pairs: Two different pairs.
  • Three of a Kind: Three cards of the same value.
  • Straight: All cards are consecutive values.
  • Flush: All cards of the same suit.
  • Full House: Three of a kind and a pair.
  • Four of a Kind: Four cards of the same value.
  • Straight Flush: All cards are consecutive values of same suit.
  • Royal Flush: Ten, Jack, Queen, King, Ace, in same suit.

The cards are valued in the order:

2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, Queen, King, Ace.

If two players have the same ranked hands then the rank made up of the highest value wins; for example, a pair of eights beats a pair of fives (see example 1 below). But if two ranks tie, for example, both players have a pair of queens, then highest cards in each hand are compared (see example 4 below); if the highest cards tie then the next highest cards are compared, and so on.

Consider the following five hands dealt to two players:


The file, poker.txt, contains one-thousand random hands dealt to two players. Each line of the file contains ten cards (separated by a single space): the first five are Player 1’s cards and the last five are Player 2’s cards. You can assume that all hands are valid (no invalid characters or repeated cards), each player’s hand is in no specific order, and in each hand there is a clear winner.

How many hands does Player 1 win?


open System.IO

// define the suits
type Suit = | Heart | Club | Spade | Diamond

// define the card values
type CardValue =
    | ValueCard of int | Jack | Queen | King | Ace
    // define the + unary operator on the CardValue type
    static member (~+) (value:CardValue) =
        match value with
        | ValueCard n -> if n < 10 then ValueCard (n+1) else Jack
        | Jack -> Queen | Queen -> King | King -> Ace | Ace -> ValueCard(2)

// define the Card type
type Card = { Value:CardValue; Suit:Suit }

// define the ranks
type Rank =
    | HighCard of CardValue
    | OnePair of CardValue
    | TwoPairs of CardValue * CardValue
    | Three of CardValue
    | Straight of CardValue // highest value card
    | Flush
    | FullHouse of CardValue * CardValue
    | Four of CardValue
    | StraightFlush of CardValue // highest value card & suit
    | RoyalFlush

// define function to check if cards are the same suit
let isSameSuit (cards:Card list) = cards |> List.forall (fun c -> c.Suit = cards.Head.Suit)

// define function to get the highest card
let getHighestCard(cards:Card list) = cards |> List.maxBy (fun card -> card.Value)

// define function to check if cards are a straight set
let isStraight (cards:Card list) =
    let flag = cards |> List.sort |> Seq.windowed 2
    |> Seq.forall (fun [|card1; card2|] -> card2.Value = +card1.Value)

    (flag, getHighestCard cards)

// define function to get the duplicated cards
let getDupes (cards:Card list) =
    |> Seq.groupBy (fun card -> card.Value)
    |> (fun (k, seq) -> (k, Seq.length seq))
    |> Seq.filter (fun (k, len) -> len > 1)
    |> Seq.toList

// evaluate the given hand of cards to return the rank
let evaluateHand (cards:Card list) =
    let (isStraight, highCard) = isStraight cards
    let sameSuit = isSameSuit cards
    if sameSuit && isStraight && highCard.Value = Ace then RoyalFlush
    else if sameSuit && isStraight then StraightFlush(highCard.Value)
    else if sameSuit then Flush
    else if isStraight then Straight(highCard.Value)
        let dupes = getDupes cards
        if dupes.Length = 0 then HighCard(highCard.Value)
        else if dupes.Length = 1 then
            match dupes.[0] with
            | (cardValue,2) -> OnePair(cardValue)
            | (cardValue,3) -> Three(cardValue)
            | (cardValue,4) -> Four(cardValue)
            match dupes with
            | [(cardValue',2);(cardValue'',2)] -> TwoPairs(cardValue', cardValue'')
            | [(cardValue',3);(cardValue'',2)] -> FullHouse(cardValue', cardValue'')
            | [(cardValue',2);(cardValue'',3)] -> FullHouse(cardValue'', cardValue')

// define function to check if player 1 wins
let isP1Winner (p1:Card list) (p2:Card list) =
    let p1Rank, p2Rank = evaluateHand p1, evaluateHand p2

    if p1Rank > p2Rank then true
    else if p1Rank = p2Rank then
        let rec compareHighCard (p1':Card list) (p2':Card list) =
            let p1'HighCard, p2'HighCard = getHighestCard p1', getHighestCard p2'
            if p1'HighCard.Value > p2'HighCard.Value then true
            else if p1'HighCard.Value = p2'HighCard.Value then
                let p1'' = p1' |> List.filter (fun c -> c.Value < p1'HighCard.Value)
                let p2'' = p2' |> List.filter (fun c -> c.Value < p2'HighCard.Value)
                compareHighCard p1'' p2''
            else false

        compareHighCard p1 p2
    else false

// define function to parse, say, "3D" into a 3 of Diamond
let parseCard &#91;|(value:char);(suit:char)|&#93; =
    let cardValue =
        match value with
        | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' -> ValueCard(int(value.ToString()))
        | 'T' -> ValueCard(10) | 'J' -> Jack | 'Q' -> Queen | 'K' -> King | 'A' -> Ace
    let suit =
        match suit with
        | 'S' -> Spade | 'H' -> Heart | 'D' -> Diamond | 'C' -> Club
    { Card.Value=cardValue; Suit=suit }

// get the array of all the dealt hands from the poker text file
let hands =
    |> (fun str -> 
        str.Split(' ') 
        |> (fun str -> parseCard (str.ToCharArray())))

// separate the hands into two different arrays, one for player 1 and the other player 2
let p1Hands = hands |> (fun cards -> cards |> Seq.take 5 |> Seq.toList)
let p2Hands = hands |> (fun cards -> cards |> Seq.skip 5 |> Seq.toList)

let answer =
    Array.map2 (fun p1 p2 -> isP1Winner p1 p2) p1Hands p2Hands
    |> Array.filter (fun b -> b)
    |> Array.length
Liked this article? Support me on Patreon and get direct help from me via a private Slack channel or 1-2-1 mentoring.
Subscribe to my newsletter

Hi, I’m Yan. I’m an AWS Serverless Hero and I help companies go faster for less by adopting serverless technologies successfully.

Are you struggling with serverless or need guidance on best practices? Do you want someone to review your architecture and help you avoid costly mistakes down the line? Whatever the case, I’m here to help.

Hire me.

Skill up your serverless game with this hands-on workshop.

My 4-week Production-Ready Serverless online workshop is back!

This course takes you through building a production-ready serverless web application from testing, deployment, security, all the way through to observability. The motivation for this course is to give you hands-on experience building something with serverless technologies while giving you a broader view of the challenges you will face as the architecture matures and expands.

We will start at the basics and give you a firm introduction to Lambda and all the relevant concepts and service features (including the latest announcements in 2020). And then gradually ramping up and cover a wide array of topics such as API security, testing strategies, CI/CD, secret management, and operational best practices for monitoring and troubleshooting.

If you enrol now you can also get 15% OFF with the promo code “yanprs15”.

Enrol now and SAVE 15%.

Check out my new podcast Real-World Serverless where I talk with engineers who are building amazing things with serverless technologies and discuss the real-world use cases and challenges they face. If you’re interested in what people are actually doing with serverless and what it’s really like to be working with serverless day-to-day, then this is the podcast for you.

Check out my new course, Learn you some Lambda best practice for great good! In this course, you will learn best practices for working with AWS Lambda in terms of performance, cost, security, scalability, resilience and observability. We will also cover latest features from re:Invent 2019 such as Provisioned Concurrency and Lambda Destinations. Enrol now and start learning!

Check out my video course, Complete Guide to AWS Step Functions. In this course, we’ll cover everything you need to know to use AWS Step Functions service effectively. There is something for everyone from beginners to more advanced users looking for design patterns and best practices. Enrol now and start learning!