chore: update 2026-04-21
This commit is contained in:
1000
solutions/0054.PokerHands/0054_poker.txt
Normal file
1000
solutions/0054.PokerHands/0054_poker.txt
Normal file
File diff suppressed because it is too large
Load Diff
219
solutions/0054.PokerHands/euler_54.py
Normal file
219
solutions/0054.PokerHands/euler_54.py
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
"""
|
||||||
|
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 2C 3S 8S 8D TD Player 2
|
||||||
|
Pair of Fives Pair of Eights
|
||||||
|
2 5D 8C 9S JS AC 2C 5C 7D 8S QH Player 1
|
||||||
|
Highest card Ace Highest card Queen
|
||||||
|
3 2D 9C AS AH AC 3D 6D 7D TD QD Player 2
|
||||||
|
Three Aces Flush with Diamonds
|
||||||
|
4 4D 6S 9H QH QC 3D 6D 7H QD QS Player 1
|
||||||
|
Pair of Queens Pair of Queens
|
||||||
|
Highest card Nine Highest card Seven
|
||||||
|
5 2H 2D 4C 4D 4S 3C 3D 3S 9S 9D Player 1
|
||||||
|
Full House Full House
|
||||||
|
With Three Fours with Three Threes
|
||||||
|
|
||||||
|
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?
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
from collections import defaultdict
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def timer(func):
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
start_time = time.time()
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
end_time = time.time()
|
||||||
|
elapsed_time = end_time - start_time
|
||||||
|
print(f"{func.__name__} time: {elapsed_time:.6f} seconds")
|
||||||
|
return result
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
class PokerHand:
|
||||||
|
def __init__(self, hand: tuple[str, ...]):
|
||||||
|
self.hand = hand
|
||||||
|
self._rank = self.get_rank()
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
return len(self.hand)
|
||||||
|
|
||||||
|
def _is_same_suit(self) -> bool:
|
||||||
|
return len(set(card[1] for card in self.hand)) == 1
|
||||||
|
|
||||||
|
def _is_flush(self) -> bool:
|
||||||
|
keyflush = "23456789TJQKA"
|
||||||
|
index = []
|
||||||
|
for card in self.hand:
|
||||||
|
index.append(keyflush.index(card[0]))
|
||||||
|
if len(set(index)) != 5:
|
||||||
|
return False
|
||||||
|
return (max(index) - min(index)) == (self.__len__() - 1)
|
||||||
|
|
||||||
|
def _high_card(self) -> int:
|
||||||
|
key = "23456789TJQKA"
|
||||||
|
return max(key.index(card[0]) for card in self.hand) + 1
|
||||||
|
|
||||||
|
def _same_number(self) -> tuple[int | None, int | None, int | None]:
|
||||||
|
keyflush = "23456789TJQKA"
|
||||||
|
counts = defaultdict(int)
|
||||||
|
for card in self.hand:
|
||||||
|
if card[0] in counts:
|
||||||
|
counts[card[0]] += 1
|
||||||
|
else:
|
||||||
|
counts[card[0]] = 1
|
||||||
|
if 2 in counts.values():
|
||||||
|
pair = list(
|
||||||
|
keyflush.index(key) + 1 for key, value in counts.items() if value == 2
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
pair = []
|
||||||
|
max_count = max(counts.values())
|
||||||
|
if max_count > 1:
|
||||||
|
num = max(
|
||||||
|
keyflush.index(key) + 1
|
||||||
|
for key, value in counts.items()
|
||||||
|
if value == max_count
|
||||||
|
)
|
||||||
|
if max_count == 2:
|
||||||
|
return 2, num, min(pair) if pair else None
|
||||||
|
return max_count, num, max(pair) if pair else None
|
||||||
|
return None, None, None
|
||||||
|
|
||||||
|
def get_rank(self) -> tuple[int | None, ...]:
|
||||||
|
res: list[int | None] = [None for _ in range(10)]
|
||||||
|
if self._is_same_suit():
|
||||||
|
if sorted(card[0] for card in self.hand) == list("AJKQT"):
|
||||||
|
res[0] = 1
|
||||||
|
res[1] = 1
|
||||||
|
res[4] = self._high_card()
|
||||||
|
res[5] = self._high_card()
|
||||||
|
elif self._is_flush():
|
||||||
|
res[0] = 0
|
||||||
|
res[1] = 1
|
||||||
|
res[4] = self._high_card()
|
||||||
|
res[5] = self._high_card()
|
||||||
|
else:
|
||||||
|
res[0] = 0
|
||||||
|
res[1] = 0
|
||||||
|
res[4] = self._high_card()
|
||||||
|
res[5] = 0
|
||||||
|
else:
|
||||||
|
if self._is_flush():
|
||||||
|
res[5] = self._high_card()
|
||||||
|
else:
|
||||||
|
res[5] = 0
|
||||||
|
res[0] = 0
|
||||||
|
res[1] = 0
|
||||||
|
res[4] = 0
|
||||||
|
max_count, num, pair = self._same_number()
|
||||||
|
if max_count is not None:
|
||||||
|
if max_count == 4:
|
||||||
|
res[2] = num
|
||||||
|
res[3] = 0
|
||||||
|
res[6] = 0
|
||||||
|
res[7] = 0
|
||||||
|
res[8] = 0
|
||||||
|
elif max_count == 3:
|
||||||
|
res[2] = 0
|
||||||
|
res[6] = num
|
||||||
|
res[7] = 0
|
||||||
|
if pair is not None:
|
||||||
|
res[3] = 1
|
||||||
|
res[8] = pair
|
||||||
|
else:
|
||||||
|
res[3] = 0
|
||||||
|
res[8] = 0
|
||||||
|
elif max_count == 2:
|
||||||
|
res[2] = 0
|
||||||
|
res[3] = 0
|
||||||
|
res[6] = 0
|
||||||
|
if pair is not None and pair != num:
|
||||||
|
res[7] = 1
|
||||||
|
res[8] = num
|
||||||
|
else:
|
||||||
|
res[7] = 0
|
||||||
|
res[8] = num
|
||||||
|
else:
|
||||||
|
res[2] = 0
|
||||||
|
res[3] = 0
|
||||||
|
res[6] = 0
|
||||||
|
res[7] = 0
|
||||||
|
res[8] = 0
|
||||||
|
res[9] = self._high_card()
|
||||||
|
return tuple(res)
|
||||||
|
|
||||||
|
def __gt__(self, other: "PokerHand") -> bool:
|
||||||
|
if len(self) != len(other):
|
||||||
|
raise ValueError("Cannot compare hands of different lengths")
|
||||||
|
for a, b in zip(self._rank, other._rank):
|
||||||
|
if a is None:
|
||||||
|
if b is not None:
|
||||||
|
return False
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
if b is None:
|
||||||
|
return True
|
||||||
|
if a != b:
|
||||||
|
return a > b
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def read_poker_hands() -> list[tuple[tuple[str, ...], tuple[str, ...]]]:
|
||||||
|
rPath = Path(__file__).parent / "0054_poker.txt"
|
||||||
|
with open(rPath, "r") as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
res = []
|
||||||
|
for line in lines:
|
||||||
|
tmp = line.replace("\n", "").split(" ")
|
||||||
|
if len(tmp) < 10:
|
||||||
|
raise ValueError("Invalid line in poker.txt")
|
||||||
|
res.append((tuple(tmp[:5]), tuple(tmp[5:])))
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
@timer
|
||||||
|
def main() -> int:
|
||||||
|
player1_win = 0
|
||||||
|
data = read_poker_hands()
|
||||||
|
for hand1, hand2 in data:
|
||||||
|
if PokerHand(hand1) > PokerHand(hand2):
|
||||||
|
player1_win += 1
|
||||||
|
return player1_win
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
player1_win = main()
|
||||||
|
print(f"Player 1 wins {player1_win} hands out of {len(read_poker_hands())}.")
|
||||||
101
solutions/0054.PokerHands/euler_54_better.py
Normal file
101
solutions/0054.PokerHands/euler_54_better.py
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import time
|
||||||
|
from collections import Counter
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def timer(func):
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
start_time = time.time()
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
end_time = time.time()
|
||||||
|
elapsed_time = end_time - start_time
|
||||||
|
print(f"{func.__name__} time: {elapsed_time:.6f} seconds")
|
||||||
|
return result
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
# 牌面数值映射
|
||||||
|
VAL = {
|
||||||
|
"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_key(cards: tuple[str, ...]) -> tuple[int, ...]:
|
||||||
|
"""
|
||||||
|
将一手5张牌映射为规范元组。
|
||||||
|
元组的字典序即牌力大小,可直接用 > / < 比较。
|
||||||
|
"""
|
||||||
|
# 1. 提取数值并降序排列
|
||||||
|
nums = sorted([VAL[c[0]] for c in cards], reverse=True)
|
||||||
|
suits = {c[1] for c in cards}
|
||||||
|
|
||||||
|
# 2. 判断同花与顺子
|
||||||
|
is_flush = len(suits) == 1
|
||||||
|
is_straight = len(set(nums)) == 5 and nums[0] - nums[4] == 4
|
||||||
|
|
||||||
|
# 3. 处理 A-2-3-4-5 顺子(A作为1,5-high)
|
||||||
|
if nums == [14, 5, 4, 3, 2]:
|
||||||
|
is_straight = True
|
||||||
|
nums = [5, 4, 3, 2, 1]
|
||||||
|
|
||||||
|
# 4. 统计出现次数,并按 (次数, 牌面) 降序排列
|
||||||
|
# 这是整个方法的数学核心:多重集的字典序
|
||||||
|
counts = Counter(nums)
|
||||||
|
by_count = sorted(counts.items(), key=lambda x: (x[1], x[0]), reverse=True)
|
||||||
|
|
||||||
|
# 5. 按牌型返回规范元组
|
||||||
|
if is_flush and is_straight:
|
||||||
|
return (9, nums[0]) # 同花顺(皇家同花顺只是顶牌为A)
|
||||||
|
if by_count[0][1] == 4:
|
||||||
|
return (8, by_count[0][0], by_count[1][0]) # 四条
|
||||||
|
if by_count[0][1] == 3 and len(by_count) == 2:
|
||||||
|
return (7, by_count[0][0], by_count[1][0]) # 葫芦
|
||||||
|
if is_flush:
|
||||||
|
return (6, *nums) # 同花:比全部5张降序
|
||||||
|
if is_straight:
|
||||||
|
return (5, nums[0]) # 顺子:比顶牌
|
||||||
|
if by_count[0][1] == 3:
|
||||||
|
return (4, by_count[0][0], by_count[1][0], by_count[2][0]) # 三条
|
||||||
|
if by_count[0][1] == 2 and len(by_count) == 3:
|
||||||
|
return (3, by_count[0][0], by_count[1][0], by_count[2][0]) # 两对
|
||||||
|
if by_count[0][1] == 2:
|
||||||
|
return (
|
||||||
|
2,
|
||||||
|
by_count[0][0],
|
||||||
|
by_count[1][0],
|
||||||
|
by_count[2][0],
|
||||||
|
by_count[3][0],
|
||||||
|
) # 一对
|
||||||
|
return (1, *nums) # 高牌:比全部5张降序
|
||||||
|
|
||||||
|
|
||||||
|
def read_hands(path: Path):
|
||||||
|
with open(path) as f:
|
||||||
|
for line in f:
|
||||||
|
cards = line.strip().split()
|
||||||
|
if len(cards) == 10:
|
||||||
|
yield tuple(cards[:5]), tuple(cards[5:])
|
||||||
|
|
||||||
|
|
||||||
|
@timer
|
||||||
|
def main() -> int:
|
||||||
|
data_path = Path(__file__).parent / "0054_poker.txt"
|
||||||
|
wins = sum(1 for p1, p2 in read_hands(data_path) if hand_key(p1) > hand_key(p2))
|
||||||
|
return wins
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print(main())
|
||||||
Reference in New Issue
Block a user