【发布时间】:2019-12-21 10:23:58
【问题描述】:
首先声明:我是一名医疗专业人士,我的爱好是玩 Python 和扑克。我没有接受过这些方面的正式培训,也不知道计算机科学课程的课程内容。 我使用的计算机是 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 - 那是一个巨大的数据库吗?
最后我做了一个完整的手与手评估器,在伪代码中这样做:
给定 2 手牌,例如 AhAs 与 6d6h
列出所有可以处理这 2 张“死”牌的棋盘,即 1 712 304 个棋盘
列出hand1 与棋盘1 的所有21 种组合,
使用这 21 种组合搜索 rank_hands 字典并返回可能的最佳结果(21 种组合,因为在德州扑克中,您可以使用手牌中的一张、两张或不使用手牌,并使用 5 张公共牌中的任何一张)
对 hand2 和 board1 执行相同操作
比较hand1的最佳结果和hand2的最佳结果
计算结果是否有利于手 1、手 2 或平局
转到下一个板
该算法进行了大约 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 中
【问题讨论】:
-
代码在哪里?
-
也许更适合Code Review,尽管他们需要代码审查。
-
我有兴趣查看和审查代码,但正如@Sayse 指出的那样,代码审查可能会更好,因为这对于 SO 问题来说非常广泛。如果您选择在 CR 上发帖,请在此处链接,我会看看。对于这类组合数学问题,使用字典通常是一个好主意,但听起来在您的情况下,您的字典结构不是特别有用。
-
对不起。我不知道有不同的平台可以提出不同的问题,例如 StackOverflow 和代码审查。如前所述,业余爱好者。添加了代码。
-
标签: python combinatorics poker