【问题标题】:How can I speed up my Python poker hand vs. hand equity calculator [closed]如何加快我的 Python 扑克手与手牌净值计算器 [关闭]
【发布时间】: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


【解决方案1】:

我注意到您方法中的一个广泛主题可能需要重新评估,因为它可能会对性能产生重大影响。

听起来你正试图暴力破解这个问题。如果我现在让您比较 2 手牌(没有计算机,只有您的大脑),您是否会参考您存储在内存中的所有可能的扑克手牌的预先计算列表?你有没有看过这样的清单(实际上是坐下来通读每一行)?我希望不会,我猜这两个问题的答案都是“否”。

那么,您为什么选择该策略来解决您的程序中的相同问题?相反,您能否编写一个程序,其中包含每种扑克手类型的抽象定义?这样您的程序就能够识别“皇家同花顺”或“满堂彩”?然后它只需要计算有问题的 2 手的相对值,并比较结果以确定更好的手。没有要扫描的大查找表,我敢打赌,无需比您已有的代码多太多代码就可以完成(但您可能需要放弃已有的内容并重新开始)。

如果您仍想采用预先计算的查找表的策略,这里有几个建议:

  • 从牌桌中删除所有不可玩的牌。由于无论如何您都在存储数据,因此您只需执行一次。然后,您可以将每个失败的查找默认为零分。至少,如果您需要对所有元素进行线性扫描,这将节省空间并减少所需的时间。说起来……
  • Python 字典基本上使用hash table 作为将键映射到关联值的底层实现:{ key : value }。这意味着,当通过指定整个键而不是其他任何内容 (my_dict[key]) 访问记录时,可以在固定的时间内完成操作,该时间不会随着表的增长而增加(如与列表相反,列表需要线性遍历列表,直到找到匹配的记录,或者在没有匹配的情况下检查了所有记录)。创建字典时,请确保键的构造方式与以后访问它的方式完全匹配。

而且,无论您选择哪种方法:

  • 由于您将其作为学习练习,我强烈建议您不要使用itertools,也可能使用collections。虽然这些是非常方便的库,但它们也隐藏了算法设计的一些非常基本和重要的方面。如果这被分配为本科计算机科学课程的家庭作业,那么这些图书馆很可能不会被允许。 This article 比我解释得更好(而且是 2001 年的乔尔,你还能要求什么?)。
  • 同样,由于这是为了学习,我建议您学习使用 Python 的调试工具。具体来说,学习如何在执行期间设置断点和暂停程序,这使您能够逐行执行代码。这将有助于揭示代码中的热点,以便您了解最好将时间花在哪里来提高性能。

编辑

这是一个实现一手牌的类,并通过使用“>”、“

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...]

【讨论】:

  • 这是我最初的想法,但如果你查看原始问题中指向 Quora statitistics 页面的链接,你会发现极少数牌构成的牌除了“无”和“一对”之外的任何东西对于那些手,你需要完整的评估直到最后一个踢球者。所以我认为任何“捷径”都会比它的价值更多。
  • @rioZg 在所有可能的牌组中只有这么少的可玩牌,这正是使动态计算变得有利的原因。所需要的只是编写一些函数来确定它们的输入是否与特定模式匹配,然后返回为输入分配数字分数所需的详细信息(可能是花色/高牌)。更好的是:只有 8 种情况下所有可玩的手都会落入其中,而且它们都易于用 Python 代码表达。我想如果您尝试这种方法,您会发现这是一个非常简单的解决方案。
  • 在这 8 种特殊情况下,无需评估任何给定的手牌:如果一只手牌没有落入其中一种,则自动为零分。
  • 不是真正的 Z4 层。棋盘是 9 8 7 3 2 ,一个玩家持有 A6 另一个 A4,玩家 1 的最佳手牌是 A 9 8 7 6,对于玩家 2 A 9 8 7 4,玩家 1 在最后一个踢球者中获胜。牌面是 As 3s 4s 5s Qd ,player1 拿着 2s2d,player 2 拿着 KsKd,player 3 拿着 8s7s,你需要的不仅仅是 player 2 有同花,player 3 有同花,因为 player 2 击败了 player 3 但输给了 player一个患有直流感的人。所以知道这手牌是零分是不够的。
  • 我刚刚用一个适用于任何手的工作示例更新了这个答案。它首先将手牌分配给一个类别(即 SF、FH、3K 等),然后维护一张牌列表,可用于确定同一类别中 2 手牌的相对顺序。这利用了这样一个事实,即无论手牌类别如何,决胜局都遵循相同的算法;唯一改变的是比较的特定卡片。这个问题的变体经常在编程比赛中使用。
【解决方案2】:

您可以使用 dbm 模块(请参阅 https://docs.python.org/3/library/dbm.html)或 python 2.x 中的 bsddb 将整个查找表存储在数据库文件中,如果它太大而无法放入字典中的内存。然后可能需要一些时间来填写表格,但您只需填写一次。

【讨论】:

    【解决方案3】:

    我现在已经更仔细地阅读了您的代码。我认为预先计算每手牌的等级的方法是可行的,尽管它看起来很暴力。您关于在大多数情况下必须评估每个踢球者的观点似乎是这种方法的合理理由。

    编辑 - 一些在线研究表明这不是一个微不足道的问题,当前最好的解决方案是“2+2”方法,它实际上基于查找但进行了一些重度优化。 https://web.archive.org/web/20111103160502/http://www.codingthewheel.com/archives/poker-hand-evaluator-roundup#2p2

    一些一般要点:

    • 我并没有真正尝试优化您的 hand_rank_dict 函数,因为它只需要运行一次。
    • 话虽如此。将 itertools 迭代器转换为列表没有任何好处,您只是通过将它们保留在周围来占用内存。仅仅通过改变这个,我在构建字典的运行时得到了相当大的改进

    但是,我一直在努力提高代码的速度。事实上,我认为它现在慢了一点!我通过在可能的情况下使用集合并删除一些不必要的中间变量来稍微整理一下,但从根本上说,绝大多数运行时间似乎都被字典查找所消耗。每一个都非常快,但有这么多加起来。这里值得的是我的代码版本。我没有使用 Jupyter,所以我稍微重构以将字典保存到磁盘。我还没有想到比你更好的算法,所以我会继续思考!

    import itertools
    import time
    import pickle
    
    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()
    suitsall = "hearts spades diamonds clubs"
    suitnames = suitsall.split()
    suits = ['h','s','d','c']
    cards = set()
    
    # Create all cards from suits and ranks
    for suit in suits:
        for rank in ranks:
            cards.add(rank + suit)
    
    
    # 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}
    
    
    def build_hands_dict(cards, path):
    
        ranked_hands_dict = {}
        t0 = time.time()
        for board in itertools.combinations(cards, 5):
            ranked_hands_dict.update(hand_rank_dict(board))
        t1 = time.time()
        total = t1-t0
        print(total)
        with open(path,'wb') as f:
            pickle.dump(ranked_hands_dict, f)
    
    # Uncomment this to build the pre-calculated dict of hand ranks
    # build_hands_dict(cards, r'D:\hands.p')
    
    with open(r'D:\hands.p','rb') as f:
        ranked_hands_dict = pickle.load(f)
    
    # 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, hand):
    
        seven_card_hand = set(board) | hand
        evaluated_all_possible_hands = []
    
        all_possible_hands = 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)
    
    
    hand1 = {'2h', '7d'}
    hand2 = {'Ad', 'Ah'}
    
    # HAND vs. HAND EVALUATOR
    
    t0 = time.time()
    
    one = 0
    two = 0
    tie = 0
    
    deadcards = hand1 | hand2
    possible_boards = itertools.combinations(cards - deadcards, 5)
    
    n = 0
    for board in possible_boards:
    
        hand1rank = find_the_best_hand(board, hand1)
        hand2rank = find_the_best_hand(board, hand2)
    
        if hand1rank > hand2rank:
            one = one + 1
    
        elif hand1rank < hand2rank:
            two = two + 1
    
        else: #hand1rank == hand2rank:
            tie = tie + 1
    
        n += 1
    
    onepercent = (one/n)*100
    twopercent = (two/n)*100
    tiepercent = (tie/n)*100
    
    print(onepercent, twopercent, tiepercent)
    
    
    t1 = time.time()
    
    total = t1-t0
    
    print(total)
    

    【讨论】:

    • ...手牌对抗手牌或范围赢率是每个玩家工具箱中必不可少的工具。几乎每一手你都参与其中。
    • 好吧,我对扑克一无所知,但我认为你的手牌只有你自己可见,所以手牌对战更像是一种学术练习,而不是游戏中的工具。
    • 感谢您的链接。很有用。但这似乎超出了我的能力水平,特别是考虑到所有这些都是我不熟悉的编程语言,所以我无法轻易评估他们是如何做到的。但是,如果他们在 2006 年对旧计算机每秒进行 15-16 百万次评估,那对我来说是非常可以接受的。在所有可能的板上对手牌与手牌的全面评估大约有 7200 万次评估。
    • 我不得不承认我还没有完全理解它,但这足以让我相信我们不会轻易改进你现有的算法。它看起来像是一个非常精细的查找系统,经过优化以利用扑克牌的确切特征。话虽如此,如果您针对多处理(Python 默认仅使用一个 CPU 线程)和/或使用更快的语言对其进行优化,您可能会更快地获得现有算法。我喜欢 Python,但如果您正在寻找最快的执行速度,那么它远非最佳选择。
    • 感谢 SimonN。我在寻找解决方案时突然想到,离开 Python 去“更绿色的牧场”可能是唯一的解决方案。
    猜你喜欢
    • 2015-10-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多