Poker hands

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:

Hand Player 1 Player 2 Winner
1 5H 5C 6S 7S KD
Pair of Fives
 2C 3S 8S 8D TD
Pair of Eights
 Player 2
2 5D 8C 9S JS AC
Highest card Ace
 2C 5C 7D 8S QH
Highest card Queen
 Player 1
3 2D 9C AS AH AC
Three Aces
 3D 6D 7D TD QD
Flush with Diamonds
 Player 2
4 4D 6S 9H QH QC
Pair of Queens
Highest card Nine
 3D 6D 7H QD QS
Pair of Queens
Highest card Seven
 Player 1
5 2H 2D 4C 4D 4S
Full House
With Three Fours
 3C 3D 3S 9S 9D
Full House
with Three Threes
 Player 1

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?


Idea

Quite direct solution. Just be patient.

From highest to lowest, check if a certain rank pattern exists in player's hand cards.


In [1]:
from urllib.request import urlopen
from collections import Counter
In [2]:
with urlopen('https://projecteuler.net/project/resources/p054_poker.txt') as f:
    resp = f.read().decode('utf-8')
In [3]:
def transform_value(v):
    if '2' <= v <= '9':
        return int(v)
    else:
        if v == 'T':
            return 10
        elif v == 'J':
            return 11
        elif v == 'Q':
            return 12
        elif v == 'K':
            return 13
        elif v == 'A':
            return 14
        else:
            raise ValueError('Wrong representation')
In [4]:
[transform_value(v) for v in list(map(str, range(2, 10))) + ['T', 'J', 'Q', 'K', 'A']]
Out[4]:
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
In [5]:
def transform_hand(hand):
    values = list(map(transform_value, [c[0] for c in hand]))
    suits = [c[1] for c in hand]
    return sorted(zip(values, suits))
In [6]:
transform_hand(['5H', '5C', '6S', '7S', 'KD'])
Out[6]:
[(5, 'C'), (5, 'H'), (6, 'S'), (7, 'S'), (13, 'D')]
In [7]:
def split_hand(hand):
    cards = hand.split(' ')
    p1, p2 = cards[:5], cards[-5:]
    return transform_hand(p1), transform_hand(p2)
In [8]:
split_hand('5H 5C 6S 7S KD 2C 3S 8S 8D TD')
Out[8]:
([(5, 'C'), (5, 'H'), (6, 'S'), (7, 'S'), (13, 'D')],
 [(2, 'C'), (3, 'S'), (8, 'D'), (8, 'S'), (10, 'D')])
In [9]:
def generate_hand(text):
    hands = text.splitlines()
    for hand in hands:
        yield split_hand(hand)
In [10]:
def get_royal_flush(hand):
    if hand[0][0] == 10 and hand[-1][0] == 14 and \
        len([c[0] for c in hand]) == len(set([c[0] for c in hand])) and \
        len(set([c[1] for c in hand])) == 1:
        return 'BOOM'
    else:
        return None
In [11]:
get_royal_flush([(10, 'C'), (11, 'C'), (12, 'C'), (13, 'C'), (14, 'C')])
Out[11]:
'BOOM'
In [12]:
get_royal_flush([(10, 'C'), (11, 'C'), (12, 'C'), (12, 'C'), (14, 'C')])
In [13]:
def get_straight_flush(hand):
    if len([c[0] for c in hand]) == len(set([c[0] for c in hand])) and \
        hand[-1][0] - hand[0][0] == 4 and \
        len(set([c[1] for c in hand])) == 1:
        return hand[0][0]
    else:
        return None
In [14]:
get_straight_flush([(2, 'C'), (3, 'C'), (4, 'C'), (5, 'C'), (6, 'C')])
Out[14]:
2
In [15]:
get_straight_flush([(2, 'C'), (3, 'C'), (4, 'C'), (5, 'C'), (10, 'C')])
In [16]:
def get_a_kind(hand):
    c = Counter([c[0] for c in hand])
    vk = [(v, k) for k, v in c.items()]
    return sorted(vk, reverse=True)    
In [17]:
get_a_kind([(2, 'C'), (2, 'H'), (2, 'S'), (3, 'C'), (4, 'C')])
Out[17]:
[(3, 2), (1, 4), (1, 3)]
In [18]:
get_a_kind([(2, 'C'), (2, 'H'), (2, 'S'), (3, 'C'), (3, 'H')])
Out[18]:
[(3, 2), (2, 3)]
In [19]:
get_a_kind([(2, 'C'), (2, 'H'), (3, 'S'), (3, 'C'), (4, 'C')])
Out[19]:
[(2, 3), (2, 2), (1, 4)]
In [20]:
get_a_kind([(2, 'C'), (2, 'H'), (5, 'S'), (3, 'C'), (4, 'C')])
Out[20]:
[(2, 2), (1, 5), (1, 4), (1, 3)]
In [21]:
get_a_kind([(2, 'C'), (10, 'H'), (11, 'S'), (3, 'C'), (4, 'C')])
Out[21]:
[(1, 11), (1, 10), (1, 4), (1, 3), (1, 2)]
In [22]:
def get_four_of_a_kind(hand):
    h = get_a_kind(hand)
    if h[0][0] == 4:
        return h
    else:
        return None
In [23]:
def get_full_house(hand):
    h = get_a_kind(hand)
    if h[0][0] == 3 and h[1][0] == 2:
        return h
    else:
        return None
In [24]:
def get_flush(hand):
    h = get_a_kind(hand)
    if len(set([c[1] for c in hand])) == 1:
        return h
    else:
        return None
In [25]:
def get_straight(hand):
    if len([c[0] for c in hand]) == len(set([c[0] for c in hand])) and \
        hand[-1][0] - hand[0][0] == 4:
        return hand[0][0]
    else:
        return None
In [26]:
def get_three_of_a_kind(hand):
    h = get_a_kind(hand)
    if h[0][0] == 3:
        return h
    else:
        return None
In [27]:
def get_two_pairs(hand):
    h = get_a_kind(hand)
    if h[0][0] == 2 and h[1][0] == 2:
        return h
    else:
        return None
In [28]:
def get_others(hand):
    return get_a_kind(hand)
In [29]:
check_ranks = [get_royal_flush, get_straight_flush, get_four_of_a_kind, 
              get_full_house, get_flush, get_straight,
               get_three_of_a_kind, get_two_pairs, get_others]
In [30]:
def solve():
    p1_win_cnt = 0
    p2_win_cnt = 0
    for p1, p2 in generate_hand(resp):
        p1_result = [rank(p1) for rank in check_ranks]
        p2_result = [rank(p2) for rank in check_ranks]
        for r1, r2 in zip(p1_result, p2_result):
            if r1 is None and r2 is None:
                continue
            elif r1 and r2 is None:
                p1_win_cnt += 1
            elif r2 and r1 is None:
                p2_win_cnt += 1
            elif r1 > r2:
                p1_win_cnt += 1
            elif r2 > r1:
                p2_win_cnt += 1
            else:
                raise ValueError('A tie occurs')
            break
    assert p1_win_cnt + p2_win_cnt == 1000
    return p1_win_cnt
In [31]:
solve()
Out[31]:
376