Peter
Peter Campbell Smith

A middling deal

Weekly challenge 291 — 14 October 2024

Week 291: 14 Oct 2024

Task 2

Task — 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.

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];
    
}

Output


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

tested 100k hands in 4 sec
tested 200k hands in 9 sec
tested 300k hands in 14 sec
tested 400k hands in 19 sec
tested 500k hands in 24 sec
tested 600k hands in 29 sec
tested 700k hands in 33 sec
tested 800k hands in 38 sec
tested 900k hands in 43 sec
tested 1000k hands in 48 sec
tested 1100k hands in 53 sec
tested 1200k hands in 57 sec
tested 1300k hands in 62 sec
tested 1400k hands in 67 sec
tested 1500k hands in 72 sec
tested 1600k hands in 77 sec
tested 1700k hands in 81 sec
tested 1800k hands in 86 sec
tested 1900k hands in 91 sec
tested 2000k hands in 96 sec
tested 2100k hands in 101 sec
tested 2200k hands in 105 sec
tested 2300k hands in 110 sec
tested 2400k hands in 115 sec
tested 2500k hands in 120 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