Peter’s blog ✴ Week 291 ✴ 14 October 2024

THE WEEKLY CHALLENGE
A middling deal

The Perl Camel

Task 2

Poker hand rankings

A draw poker hand consists of 5 cards, drawn from a pack of 52: no jokers, no wild cards. An ace can rank either high or low.

Write a script to determine the following three things:

  1. How many different 5-card hands can be dealt?
  2. How many different hands of each of the 10 ranks can be dealt? See here for descriptions of the 10 ranks of Poker hands:
  3. Check the ten numbers you get in step 2 by adding them together and showing that they're equal to the number you get in step 1.

Analysis

Well, this is a bit different! I've never been a poker player, so it's all new to me.

I think that there are two ways to approach this challenge. The first way is to look at each rank and work out how many hands will meet its rule. For example, for Straight Flush we need 5 cards of the same suit with descending ranks. The first of these cards could be anything from a King down to a 5, which is 9 possibilities. And then, these 9 possible combinations could be in any of the 4 suits, so that makes 36, added to which there are another 4 possibilities starting with a high ace, so the total is 40.

But the frequency of some of the other rank categories aren't so easy to calculate, though I'm sure it can be done.

My second approach, and the one I've coded, is to generate all the possible combinations of 5 cards, and check them successively against the top 9 rank catgories, with any hands that fail to match all of those being consigned to the 10th, High Card.

The number of different hands is 52! / (47! × 5!), which is 2598960. While it's easy to generate all these combinations using a Perl module, I wondered whether the execution time to calculate which rank category each of the 2.5 million hands falls into would be unreasonable.

But I gave it a try, and on my (quite slow) computer it manages about 20k combinations a second, or just over 2 minutes for the 2.5 million possible hands.

Is my answer correct? Fortunately, someone has done this calculation before me, and their answers are here. My answers agree with theirs, which is satisfying.

Perl Weekly’s review

from PW issue 691

I liked how a non-player takes on Poker challenge. Kudos for the effort, thanks for sharing.

Script


#!/usr/bin/perl

# Blog: http://ccgi.campbellsmiths.force9.co.uk/challenge

use v5.26;    # The Weekly Challenge - 2024-10-14
use utf8;     # Week 291 - task 2 - Poker hand rankings
use warnings; # Peter Campbell Smith
binmode STDOUT, ':utf8';
use Algorithm::Combinatorics qw(combinations);

poker_hand_rankings();

sub poker_hand_rankings {
    
    my ($i, $c, @data, $count, $card, @hand, $value, $suit, @suits, @m, @rank, @counts, $j, $combs, $secs, $start,
        $k, @ranks);
    
    say qq[\n1. How many different 5-card hands can be dealt?];
    say qq[Answer: 52! / (47! × 5!) = ] . (52 * 51 * 50 * 49 * 48 / (5 * 4 * 3 * 2));
    say '';
    
    # initialise
    @data = reverse(0 .. 51);
    $i = combinations(\@data, 5);
    $count = 0;
    @rank[$_] = 0 for 0 .. 10;
    $start = time();
    
    # loop over all possible combinations
    COMB: while ($c = $i->next) {
        
        # analyse combination
        $count ++;
        say qq[tested ] . ($count / 1000) . qq[k hands in ] . (time() - $start) . qq[ sec] if ($count % 100000 == 0);
        @hand = ();

        # split card number (0 .. 51) into suit (0 .. 3) and rank (0 .. 12)
        for $card (@$c) {
            $suit = int($card / 13);
            $value = $card % 13;
            push @hand, $suit, $value;
        }
        
        # straight flush (5 consecutive in same suit)
        if ($hand[2] == $hand[0] and $hand[4] == $hand[0] and $hand[6] == $hand[0] and $hand[8] == $hand[0]) {
            if ($hand[1] >= 4
                and $hand[3] == $hand[1] - 1 and $hand[5] == $hand[1] - 2 and $hand[7] == $hand[1] - 3 and $hand[9] == $hand[1] - 4) {
                $rank[2] ++;
                next COMB;
            }   
            if ($hand[9] == 0  # check for ace high
                and $hand[1] == 12 and $hand[3] == 11 and $hand[5] == 10 and $hand[7] == 9) {                           
                $rank[2] ++;                
                next COMB;
            }
        }
            
        # four of a kind (4 of same rank)
        @counts[$_] = 0 for 0 .. 12;
        for $j (0 .. 4) {
            $counts[$hand[2 * $j + 1]] ++;
        }
        $k = 0;
        for $j (0 .. 12) {
            if ($counts[$j] == 4) {
                $rank[3] ++;
                next COMB;
            }
        
            # full house (3 of one rank + 2 of another)
            $k |= 1 if $counts[$j] == 3;
            $k |= 2 if $counts[$j] == 2;
            if ($k == 3) {  
                $rank[4] ++;
                next COMB;
            }
        }
        
        # flush (5 of same suit)
        @counts[$_] = 0 for 0 .. 3;     
        for $j (0 .. 4) {
            $counts[$hand[2 * $j]] ++;
        }
        for $j (0 .. 3) {
            if ($counts[$j] == 5) {
                $rank[5] ++;
                next COMB;
            }
        }
        
        # straight (5 with sequential ranks)
        @ranks = sort { $a <=> $b }($hand[1], $hand[3], $hand[5], $hand[7], $hand[9]);          
        if ($ranks[4] >= 4
            and $ranks[3] == $ranks[4] - 1 and $ranks[2] == $ranks[3] - 1 
            and $ranks[1] == $ranks[2] - 1 and $ranks[0] == $ranks[1] - 1) {
            $rank[6] ++;
            next COMB;
        }
        if ($ranks[0] == 0   # check for ace high
            and $ranks[1] == 9 and $ranks[2] == 10 and $ranks[3] == 11 and $ranks[4] == 12) {
            $rank[6] ++;
            next COMB;
        }               

        
        # three of a kind (3 of same rank)
        @counts[$_] = 0 for 0 .. 12;
        for $j (0 .. 4) {
            $counts[$hand[2 * $j + 1]] ++;
        }
        $k = 0;
        for $j (0 .. 12) {
            if ($counts[$j] == 3) {
                $rank[7] ++;
                next COMB;
            }       
        }
        
        # two pair (2 of one rank, 2 of another, 1 of yet another)
        @counts[$_] = 0 for 0 .. 12;
        for $j (0 .. 4) {
            $counts[$hand[2 * $j + 1]] ++;
        }
        $k = 0;
        for $j (0 .. 12) {
            $k ++ if $counts[$j] == 2;
            if ($k == 2) {  
                $rank[8] ++;
                next COMB;
            }
        }
        
        # one pair (2 of one rank)
        @counts[$_] = 0 for 0 .. 12;
        for $j (0 .. 4) {
            $counts[$hand[2 * $j + 1]] ++
        }
        $k = 0;
        for $j (0 .. 12) {
            $k ++ if $counts[$j] == 2;
            if ($k == 1) {  
                $rank[9] ++;
                next COMB;
            }
        }
        
        # high card (anything else)
        $rank[10] ++;
    }
    
    # results
    say qq[\n2. How many different hands of each of the 10 ranks can be dealt?\n];
    say qq[Five of a kind:  ] . sprintf('%7d', $rank[1]);
    say qq[Straight flush:  ] . sprintf('%7d', $rank[2]);
    say qq[Four of a kind:  ] . sprintf('%7d', $rank[3]);
    say qq[Full house:      ] . sprintf('%7d', $rank[4]);
    say qq[Flush:           ] . sprintf('%7d', $rank[5]);
    say qq[Straight:        ] . sprintf('%7d', $rank[6]);
    say qq[Three of a kind: ] . sprintf('%7d', $rank[7]);
    say qq[Two pair:        ] . sprintf('%7d', $rank[8]);
    say qq[One pair:        ] . sprintf('%7d', $rank[9]);
    say qq[High card:       ] . sprintf('%7d', $rank[10]);
    
    say qq[\n3. Total:        $count - matches answer 1 above.\n];
    
}

97 lines of code

Output from script


1. How many different 5-card hands can be dealt?
Answer: 52! / (47! × 5!) = 2598960

tested 100k hands in 5 sec
tested 200k hands in 10 sec
tested 300k hands in 15 sec
tested 400k hands in 20 sec
tested 500k hands in 25 sec
tested 600k hands in 30 sec
tested 700k hands in 35 sec
tested 800k hands in 39 sec
tested 900k hands in 44 sec
tested 1000k hands in 49 sec
tested 1100k hands in 54 sec
tested 1200k hands in 59 sec
tested 1300k hands in 64 sec
tested 1400k hands in 69 sec
tested 1500k hands in 74 sec
tested 1600k hands in 79 sec
tested 1700k hands in 84 sec
tested 1800k hands in 88 sec
tested 1900k hands in 93 sec
tested 2000k hands in 98 sec
tested 2100k hands in 103 sec
tested 2200k hands in 108 sec
tested 2300k hands in 113 sec
tested 2400k hands in 118 sec
tested 2500k hands in 123 sec

2. How many different hands of each of the 10 ranks can be dealt?

Five of a kind:        0
Straight flush:       40
Four of a kind:      624
Full house:         3744
Flush:              5108
Straight:          10200
Three of a kind:   54912
Two pair:         123552
One pair:        1098240
High card:       1302540

3. Total:        2598960 - matches answer 1 above.

 

Any content of this website which has been created by Peter Campbell Smith is in the public domain