关闭。这个问题需要更多 focused .它目前不接受答案。
想改进这个问题?更新问题,使其仅关注一个问题 editing this post .
3个月前关闭。
Improve this question
首先声明一下:我是一名医疗专业人士,业余爱好 Python 和扑克。我没有接受过这些方面的正式培训,也不知道计算机科学类(class)的类(class)内容。
我使用的计算机是台式机 i7-4790 3.6 Ghz,具有 16 GB RAM 和 Jupyter Notebooks。
我的目标是编写相当于 pokerstrategy.com Equilab 或 https://www.cardschat.com/poker-odds-calculator.php 的代码为了我。我只会坚持德州扑克。
为此,我需要为任何 5 张牌组合编写一个评估器。我这样做了,它完美地完成了这项工作,考虑了手中的每张牌并产生一个元组作为输出,例如:
('2h', '3c', '4s', '6s', 'Jh'): (0, 11, 6, 4, 3, 2)
High-card hand, kickers J, 6, 4, 3, 2
('7c', 'Ad', 'Kd', 'Kh', 'Tc'): (1, 13, 14, 10, 7)
One pair, pair of kings, kickers A, T, 7
('2c', '3c', '4c', '5c', 'Ac'): (8, 5)
Straight flush, 5 high
所以它区分了 A 9 8 7 3 和 A 9 8 7 5 同花或高手。我检查了所有 2 598 960 张牌组合的皇家同花顺、四边形、满屋等的理论数量,并检查了频率 (https://www.quora.com/How-many-possible-hands-are-there-in-a-five-card-poker-game)
现在我尝试评估这 260 万张中每一种可能的 5 张牌组合,结果花了令人失望的 51 秒。
我有点期待认为我的 5 卡评估员不能成为算法比赛的冠军,并且肯定有更好的方法来做到这一点(如果相关,我可以在这里发布),但我认为没关系。一旦评估了所有 5 张卡片组合,我会将它们保存在字典中,下次我将加载字典,当我有任何 5 张卡片组合时,我将简单地查找结果。
另一个失望。 10 000 000(1000 万)次董事会搜索大约需要 10 次。 23-24 秒。这是我不明白的部分!!!我基本上有一个有 260 万的数据库。行 x 2 列,搜索速度非常慢。那么十亿记录数据库如何完成任何事情?我的整个字典保存到文件时需要 88 Mb - 那是一个巨大的数据库吗?
最后我做了一个完整的手与手评估器,在伪代码中这样做:
该算法进行了大约 7100 万次字典查找——每一个 170 万个棋盘 x 42(每手牌的 21 种组合两次)。
现在,这是一场灾难。每手对战约 80 秒。
以这些速度,我无法开始。
那么,任何关于我如何才能做得更好的意见都将不胜感激?
是我和我缺乏适当的计算机科学和算法知识吗?
是 Python 吗?
是 Chrome 中的 Jupyter Notebooks 吗?
还有其他建议吗?
要求的代码:
import collections
import random
import itertools
import timeit
import time
ranks = ['2','3','4','5','6','7','8','9','T','J','Q','K','A']
names ="Deuces Threes Fours Fives Sixes Sevens Eights Nines Tens Jacks Queens Kings Aces"
cardnames = names.split()
cardnames
suitsall = "hearts spades diamonds clubs"
suitnames = suitsall.split()
suitnames
suits = ['h','s','d','c']
cards = []
# Create all cards from suits and ranks
for suit in suits:
for rank in ranks:
cards.append(rank + suit)
# Create all possible flops by chosing 3 cards out of a deck
flops = list(itertools.combinations(cards, 3))
# Create all possible boards by chosing 5 cards out of a deck
boards = list(itertools.combinations(cards, 5))
# Create all possible starting hands
startingHands = list(itertools.combinations(cards, 2))
# Function dict_hand_rank ranks every board and returns a tuple (board) (value)
def hand_rank_dict(hand):
suits = []
ranks_alphabetical = []
ranks_numerical = []
ranks_histogram = []
kickers = []
kickers_text = []
isFlush = False
isStraight = False
isStraightFlush = False
handrankValue = 0
straightHeight = -1
straightName = "No straight"
handName = "none yet"
for card in hand:
suits.append(card[1])
ranks_alphabetical.append(card[0])
# create ranks_histogram where from A 2 ... J Q K A every card has the corresponding number of occurencies, A double counted
ranks_histogram.append(str(ranks_alphabetical.count('A')))
for rank in ranks:
ranks_histogram.append(str(ranks_alphabetical.count(rank)))
joined_histogram = ''.join(ranks_histogram)
# create ranks numerical instead of T, J, Q, K A
for card in hand:
ranks_numerical.append(ranks.index(card[0])+2)
# create kickers
kickers = sorted([x for x in ranks_numerical if ranks_numerical.count(x) <2], reverse = True)
# check if a hand is a straight
if '11111' in joined_histogram:
isStraight = True
straightHeight = joined_histogram.find('11111') + 5
straightName = cardnames[straightHeight - 2]
handName = "Straight"
handrankValue = (4,) + (straightHeight,)
# check if a hand is a flush
if all(x == suits[0] for x in suits):
isFlush = True
handName = "Flush " + cardnames[kickers[0] - 2] + " " + cardnames[kickers[1] - 2] \
+ " " + cardnames[kickers[2] - 2] + " " + cardnames[kickers[3] - 2] + " " + cardnames[kickers[4] - 2]
handrankValue = (5,) + tuple(kickers)
# check if a hand is a straight and a flush
if isFlush & isStraight:
isStraightFlush = True
handName = "Straight Flush"
handrankValue = (8,) + (straightHeight,)
# check if a hand is four of a kind
if '4' in joined_histogram:
fourofakindcard = (joined_histogram[1:].find('4') + 2)
handName = "Four of a Kind " + cardnames[fourofakindcard -2] + " " + cardnames[kickers[0] - 2] + " kicker"
handrankValue = (7,) + ((joined_histogram[1:].find('4') + 2),) + tuple(kickers)
# check if a hand is a full house
if ('3' in joined_histogram) & ('2' in joined_histogram):
handName = "Full house"
handrankValue = (6,) + ((joined_histogram[1:].find('3') + 2),) + ((joined_histogram[1:].find('2') + 2),) + tuple(kickers)
# check if a hand is three of a kind
if ('3' in joined_histogram) & (len(kickers) == 2):
threeofakindcard = (joined_histogram[1:].find('3') + 2)
handName = "Three of a Kind " + cardnames[threeofakindcard -2] + " " + cardnames[kickers[0] - 2] + \
" " + cardnames[kickers[1] - 2]
handrankValue = (3,) + ((joined_histogram[1:].find('3') + 2),) + tuple(kickers)
# check if a hand is two pairs
if ('2' in joined_histogram) & (len(kickers) == 1):
lowerpair = (joined_histogram[1:].find('2') + 2)
higherpair = (joined_histogram[lowerpair:].find('2') + 1 + lowerpair)
handName = "Two pair " + cardnames[higherpair -2] + " and " + cardnames[lowerpair - 2] + " " + \
cardnames[kickers[0] - 2] + " kicker"
handrankValue = (2,) + (higherpair, lowerpair) + tuple(kickers)
# check if a hand is one pair
if ('2' in joined_histogram) & (len(kickers) == 3):
lowerpair = (joined_histogram[1:].find('2') + 2)
handName = "One pair " + cardnames[lowerpair - 2] + " kickers " + cardnames[kickers[0] - 2] \
+ " " + cardnames[kickers[1] - 2] + " " + cardnames[kickers[2] - 2]
handrankValue = (1,) + (lowerpair,) + tuple(kickers)
# evaluate high card hand
if (len(ranks_numerical) == len(set(ranks_numerical))) & (isStraight == False) & (isFlush == False):
handName = "High card " + cardnames[kickers[0] - 2] + " " + cardnames[kickers[1] - 2] \
+ " " + cardnames[kickers[2] - 2] + " " + cardnames[kickers[3] - 2] + " " + cardnames[kickers[4] - 2]
handrankValue = (0,) + tuple(kickers)
return {tuple(sorted(hand)) : handrankValue}
ranked_hands_dict = {}
t0 = time.time()
for board in boards:
ranked_hands_dict.update(hand_rank_dict(board))
t1 = time.time()
total = t1-t0
# print(total)
# Function that given board and 2 cards gives back tuple of the best possible hand by searching through ranked_hands_dict keys
def find_the_best_hand(board, card1, card2):
seven_card_hand = board + (card1,) + (card2,)
evaluated_all_possible_hands = []
if (card1 in board) or (card2 in board):
return "Illegal board"
else:
all_possible_hands = list(itertools.combinations(seven_card_hand, 5))
for hand in all_possible_hands:
evaluated_all_possible_hands.append(ranked_hands_dict[tuple(sorted(hand))])
return max(evaluated_all_possible_hands)
# Function that returns a list of possible boards given the dead cards
def create_allowed_boards(cards):
list_of_allowed_boards = []
for board in boards:
if not any(karta in cards for karta in board):
list_of_allowed_boards.append(board)
return list_of_allowed_boards
hand1 = ['2h','7d']
hand2 = ['Ad','Ah']
# HAND vs. HAND EVALUATOR
t0 = time.time()
one = 0
two = 0
tie = 0
deadcards= hand1 + hand2
list_of_possible_boards = create_allowed_boards(deadcards)
for board in list_of_possible_boards:
hand1rank = find_the_best_hand(board, hand1[0], hand1[1])
hand2rank = find_the_best_hand(board, hand2[0], hand2[1])
if hand1rank > hand2rank:
one = one + 1
if hand1rank < hand2rank:
two = two + 1
if hand1rank == hand2rank:
tie = tie + 1
onepercent = (one/len(list_of_possible_boards))*100
twopercent = (two/len(list_of_possible_boards))*100
tiepercent = (tie/len(list_of_possible_boards))*100
print(onepercent, twopercent, tiepercent)
t1 = time.time()
total = t1-t0
print(total)
也许一份打印品(总计)对许多人来说,但最初是在 Jupyter Notebook 中
最佳答案
我注意到您方法中的一个广泛主题可能需要重新评估,因为它可能会对性能产生重大影响。
听起来您正试图蛮力解决这个问题。如果我现在让你比较 2 手牌(没有电脑,只有你的大脑),你会引用你存储在内存中的每一个可能的扑克手牌的预先计算列表吗?你有没有看过这样的 list (实际上是坐下来通读每一行)?我希望不会,我猜这两个问题的答案都是“不”。
那么,您为什么选择该策略来解决程序中的相同问题呢?相反,您能否编写一个包含每种牌手类型的抽象定义的程序?这样您的程序就能够识别“皇家同花顺”或“满堂彩”?然后它只需要计算有问题的 2 手的相对值,并比较结果以确定更好的手。没有要扫描的大查找表,我敢打赌,它可以在没有比你已经拥有的更多代码的情况下完成(但你可能需要放弃你已经拥有的东西并重新开始)。
如果您仍然想追求使用预先计算的查找表的策略,这里有几个建议:
{ key : value }
.这意味着当通过指定整个键而不是其他任何内容(my_dict[key]
)来访问记录时,操作可以在固定的时间内完成,不会随着表的增长而增加(与列表相反,它需要要线性遍历的列表,直到找到匹配的记录,或者检查了所有记录但没有匹配)。创建字典时,请确保构造键的方式与以后访问它的方式完全匹配。 而且,无论您选择哪种方法:
itertools
可能还有collections
.虽然这些是非常方便的库,但它们也隐藏了算法设计的一些非常基本和重要的方面。如果这被分配为本科计算机科学类(class)的家庭作业,那么这些图书馆很可能不会被允许。 This article比我解释得更好(这是 2001 年的乔尔,你还能要求什么?)。 编辑
这是一个实现一手牌的类,并通过使用“>”、“<”和“=”直接比较两个实例来为任何一组手赋予一个顺序。没有查找表。
from collections import Counter, namedtuple
SUITS = ['d', 'h', 's', 'c']
RANKS = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
Card = namedtuple('Card', ['suit', 'rank'])
class Hand:
def __init__(self, hand):
self.hand = hand
self.catg = None
self.high_card_ranks = []
self.hand.sort(key=(lambda c: c.rank), reverse=True)
self._classify_hand()
def __eq__(self, x_hand):
return self._comp_hand(x_hand) == 'EQ'
def __lt__(self, x_hand):
return self._comp_hand(x_hand) == 'LT'
def __gt__(self, x_hand):
return self._comp_hand(x_hand) == 'GT'
def __repr__(self):
face_cards = {1: 'A', 11: 'J', 12: 'Q', 13: 'K', 14: 'A'}
repr_str = ''
for n in range(0, 5):
repr_str += str(face_cards.get(self.hand[n].rank,
self.hand[n].rank)) \
+ self.hand[n].suit + ' '
return repr_str
def _classify_hand(self):
rank_freq = list(Counter(card.rank for card in self.hand).values())
suit_freq = list(Counter(card.suit for card in self.hand).values())
rank_freq.sort()
suit_freq.sort()
if self._is_straight() and suit_freq[0] == 5:
self.catg = 'SF'
self.high_card_ranks = [c.rank for c in self.hand]
self._wheel_check()
elif rank_freq[1] == 4:
self.catg = '4K'
self.high_card_ranks = [self.hand[2].rank,
(self.hand[0].rank
if self.hand[0].rank != self.hand[2].rank
else self.hand[4].rank)]
elif rank_freq[1] == 3:
self.catg = 'FH'
self.high_card_ranks = [self.hand[2].rank,
(self.hand[3].rank
if self.hand[3].rank != self.hand[2].rank
else self.hand[1].rank)]
elif suit_freq[0] == 5:
self.catg = 'F'
self.high_card_ranks = [c.rank for c in self.hand]
elif self._is_straight():
self.catg = 'S'
self.high_card_ranks = [c.rank for c in self.hand]
self._wheel_check()
elif rank_freq[2] == 3:
self.catg = '3K'
self.high_card_ranks = [self.hand[4].rank, self.hand[0].rank]
self.high_card_ranks.append(self.hand[3].rank
if self.hand[1].rank in self.high_card_ranks
else self.hand[1].rank)
elif rank_freq[2] == 2:
self.catg = '2K2'
self.high_card_ranks = [self.hand[0].rank,
self.hand[2].rank,
self.hand[4].rank]
elif rank_freq[3] == 2:
self.catg = '2K'
self.high_card_ranks = list(set(c.rank for c in self.hand))
else:
self.catg = None
self.high_card_ranks = [c.rank for c in self.hand]
def _is_straight(self):
return ((False not in [(self.hand[n].rank == self.hand[n+1].rank + 1)
for n in (0, 1, 2, 3)])
or ([c.rank for c in self.hand] == [14, 5, 4, 3, 2]))
def _wheel_check(self):
# allows for the correct ordering of low ace ("wheel") straight
if (self.catg in ['SF', 'S']
and self.high_card_ranks == [14, 5, 4, 3, 2]):
self.high_card_ranks.pop(0)
self.high_card_ranks.append(1)
def _comp_hand(self, comp_hand):
ret_val = 'EQ'
catg_order = [None, '2K', '2K2', '3K', 'S', 'F', 'FH', '4K', 'SF']
curr_hand_catg = catg_order.index(self.catg)
comp_hand_catg = catg_order.index(comp_hand.catg)
if curr_hand_catg > comp_hand_catg:
ret_val = 'GT'
elif curr_hand_catg < comp_hand_catg:
ret_val = 'LT'
else:
for curr_high_card, comp_high_card in \
zip(self.high_card_ranks, comp_hand.high_card_ranks):
if curr_high_card > comp_high_card:
ret_val = 'GT'
break
elif curr_high_card < comp_high_card:
ret_val = 'LT'
break
return ret_val
>>> from poker_hand import *
>>> h1=Hand([Card('s', 2), Card('s', 3), Card('s', 4), Card('s', 5), Card('s', 6)])
>>> h2=Hand([Card('c', 2), Card('c', 3), Card('c', 4), Card('c', 5), Card('c', 6)])
>>> h3=Hand([Card('c', 2), Card('c', 3), Card('c', 4), Card('c', 5), Card('c', 14)])
>>> h4=Hand([Card('d', 2), Card('d', 3), Card('d', 4), Card('d', 5), Card('d', 14)])
>>> h1
6s 5s 4s 3s 2s
>>> h3
Ac 5c 4c 3c 2c
>>> h1>h3
True
>>> h3>h1
False
>>> h1==h1
True
>>> h3==h4
True
>>> h2==h1
True
然后可以使用它为任意数量的玩家和套牌构建德州扑克模拟器:
from itertools import combinations, product
from random import sample, shuffle
import poker_hand
class Texas_Hold_Em(object):
def __init__(self, player_count=2):
self.player_count = player_count
self.players = []
self.comm_cards = []
self.deck = [poker_hand.Card(*c)
for c in product(poker_hand.SUITS, poker_hand.RANKS)]
def __str__(self):
face_cards = {1: 'A', 11: 'J', 12: 'Q', 13: 'K', 14: 'A'}
comm_cards = ""
for c in self.comm_cards:
comm_cards += str(face_cards.get(c.rank, c.rank)) + c.suit + " "
rv = "-" * 40 + f"\n\nCommunity Cards:\n{comm_cards}\n" + "*" * 20 + "\n"
for ct, player_hand in enumerate(self.players):
player_cards = ""
for c in player_hand:
player_cards += str(face_cards.get(c.rank, c.rank)) + c.suit + " "
rv += f"Player {str(ct)}: {player_cards}\n"
winners = self.who_wins()
rv += "*" * 20 + "\n"
for winner in winners:
rv += f"Player {str(winner[0])} wins: {str(winner[1])}\n"
rv += "\n" + "-" * 40
return rv
def deal_cards(self):
self.comm_cards.clear()
self.players.clear()
shuffle(self.deck)
dealt_cards = sample(self.deck, (2 * self.player_count) + 5)
for player in range(self.player_count):
self.players.append([dealt_cards.pop(n) for n in range(2)])
self.players[player].sort()
self.comm_cards.extend(dealt_cards)
self.comm_cards.sort()
def who_wins(self):
highest_hands = []
for player, hand in enumerate(self.players):
card_pool = self.comm_cards.copy()
card_pool.extend(hand)
card_combinations = [list(cards) for cards in combinations(card_pool, 5)]
highest_hands.append(max([poker_hand.Hand(h) for h in card_combinations]))
winning_hand = max(highest_hands)
winners = []
for player in range(highest_hands.count(winning_hand)):
idx = highest_hands.index(winning_hand)
winners.append((idx, highest_hands.pop(idx)))
return winners
然后就可以播放了:
>>> import texas_hold_em
>>> th=texas_hold_em.Texas_Hold_Em()
>>> for _ in range(10):
... th.deal_cards()
... print(th)
...
----------------------------------------
Community Cards:
3c 6c 2s 7s Js
********************
Player 0: Jc Jd
Player 1: 4c Ks
********************
Player 0 wins: Js Jc Jd 7s 6c (3K)
----------------------------------------
[etc...]
关于python - 如何加快我的 Python 扑克手与手牌赢率计算器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59435354/