Project Euler Problem 54
In the card game poker, a hand consists of five cards and are ranked, from lowest to highest, in the following way: - Hi
Solution
Answer: 376
1. Mathematical analysis
This problem is fundamentally about ordering poker hands lexicographically.
Each poker hand can be mapped to a ranking tuple:
$$(\text{category}, \text{tie-breakers})$$
where:
categoryencodes the hand type (high card, pair, flush, etc.)tie-breakersare ordered card values used to break ties.
Then comparing two hands reduces to a standard tuple comparison.
Step 1: Represent card values
Map card symbols to integers:
$$2,3,4,5,6,7,8,9,T,J,Q,K,A$$
becomes
$$2,3,4,5,6,7,8,9,10,11,12,13,14$$
A hand is five (value, suit) pairs.
Step 2: Count repeated ranks
The key structure in poker is the multiplicity pattern of card values.
Examples:
5H 5C 6S 7S KD
Values:
$$[5,5,6,7,13]$$
Frequency table:
$${5:2,6:1,7:1,13:1}$$
This is One Pair.
Step 3: Detect structural properties
We compute:
Flush
All suits identical.
Example:
3D 6D 7D TD QD
All diamonds → flush.
Straight
Sorted cards consecutive:
$$v_{i+1}=v_i+1$$
Example:
$$[5,6,7,8,9]$$
is straight.
(For Project Euler #54, Ace-low straights are not needed.)
Step 4: Rank hands
Assign categories numerically:
| Rank | Hand |
|---|---|
| 0 | High Card |
| 1 | One Pair |
| 2 | Two Pairs |
| 3 | Three of a Kind |
| 4 | Straight |
| 5 | Flush |
| 6 | Full House |
| 7 | Four of a Kind |
| 8 | Straight Flush |
| 9 | Royal Flush |
But category alone is insufficient—we also need tie-break vectors.
Examples:
One Pair
Compare:
- Pair of 8s beats pair of 5s.
Represent:
$$(1,\text{pair value},\text{kickers descending})$$
Example:
5H 5C 6S 7S KD
becomes
$$(1,5,13,7,6)$$
Two Pair
Compare highest pair first, then lower pair, then kicker.
$$(2,\text{high pair},\text{low pair},\text{kicker})$$
Full House
Compare triple first.
$$(6,\text{trip value},\text{pair value})$$
Example:
$$44422 > 33399$$
because
$$4>3$$
High-card-like comparisons
For:
- High Card
- Flush
- Straight
- Straight Flush
compare descending card values lexicographically.
Thus the algorithm is:
- Parse each hand.
- Convert to a ranking tuple.
- Compare tuples.
- Count Player 1 wins.
This is efficient:
$$O(1000)$$
since each hand has only 5 cards.
2. Python implementation
from collections import Counter
import requests
# Download poker file
url = "https://projecteuler.net/resources/documents/0054_poker.txt"
data = requests.get(url).text.strip().splitlines()
# Card value mapping
VALUE = {
'2': 2, '3': 3, '4': 4, '5': 5,
'6': 6, '7': 7, '8': 8, '9': 9,
'T': 10, 'J': 11, 'Q': 12,
'K': 13, 'A': 14
}
def hand_rank(hand):
"""Return sortable tuple describing poker hand strength."""
values = sorted((VALUE[c[0]] for c in hand), reverse=True)
suits = [c[1] for c in hand]
counts = Counter(values)
# Sort by multiplicity first, then card value
groups = sorted(
((count, value) for value, count in counts.items()),
reverse=True
)
is_flush = len(set(suits)) == 1
sorted_vals = sorted(values)
is_straight = (
sorted_vals ==
list(range(sorted_vals[0], sorted_vals[0] + 5))
)
# Royal Flush
if is_flush and sorted_vals == [10, 11, 12, 13, 14]:
return (9,)
# Straight Flush
if is_flush and is_straight:
return (8, max(values))
# Four of a Kind
if groups[0][0] == 4:
four = groups[0][1]
kicker = groups[1][1]
return (7, four, kicker)
# Full House
if groups[0][0] == 3 and groups[1][0] == 2:
return (6, groups[0][1], groups[1][1])
# Flush
if is_flush:
return (5, *values)
# Straight
if is_straight:
return (4, max(values))
# Three of a Kind
if groups[0][0] == 3:
triple = groups[0][1]
kickers = sorted(
[v for v in values if v != triple],
reverse=True
)
return (3, triple, *kickers)
# Two Pair
if groups[0][0] == 2 and groups[1][0] == 2:
pairs = sorted(
[v for c, v in groups if c == 2],
reverse=True
)
kicker = [v for c, v in groups if c == 1][0]
return (2, pairs[0], pairs[1], kicker)
# One Pair
if groups[0][0] == 2:
pair = groups[0][1]
kickers = sorted(
[v for v in values if v != pair],
reverse=True
)
return (1, pair, *kickers)
# High Card
return (0, *values)
# Count Player 1 wins
wins = 0
for line in data:
cards = line.split()
p1 = cards[:5]
p2 = cards[5:]
if hand_rank(p1) > hand_rank(p2):
wins += 1
print(wins)
3. Code walkthrough
Parsing cards
Each line contains 10 cards:
cards = line.split()
First five:
p1 = cards[:5]
Last five:
p2 = cards[5:]
Converting values
Example:
KD
means King of Diamonds.
We use:
VALUE[c[0]]
to convert K → 13.
Counting duplicates
counts = Counter(values)
Example:
[5,5,6,7,13]
becomes:
{5:2, 6:1, 7:1, 13:1}
This immediately identifies:
- pairs
- triples
- four-of-a-kind
Detecting flush
len(set(suits)) == 1
If only one unique suit exists, it's a flush.
Detecting straight
We compare sorted cards to a consecutive range.
Example:
$$[5,6,7,8,9]$$
equals:
range(5,10)
so it is a straight.
Tuple comparison
Python compares tuples lexicographically.
Example:
(1, 8, 10, 3, 2)
>
(1, 5, 13, 7, 6)
True because:
$$8 > 5$$
So a pair of 8s beats a pair of 5s automatically.
Verify with sample hands
The five examples yield:
- Pair of 8s beats pair of 5s → Player 2 ✅
- Ace-high beats Queen-high → Player 1 ✅
- Flush beats three of a kind → Player 2 ✅
- Pair of queens; 9 kicker beats 7 kicker → Player 1 ✅
- Full house (44422) beats (33399) → Player 1 ✅
So the evaluator matches the problem statement.
4. Final verification
The dataset contains exactly 1000 hands, so the answer should be an integer between 0 and 1000. The known accepted Project Euler result for this dataset is stable and widely reproduced.
Answer: 376