python - 如何加快我的 Python 扑克手与手牌赢率计算器

标签 python combinatorics poker

关闭。这个问题需要更多 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 - 那是一个巨大的数据库吗?

最后我做了一个完整的手与手评估器,在伪代码中这样做:
  • 给定 2 手牌,例如 AhAs 与 6d6h
  • 列出所有可以处理这 2 张“死”牌的棋盘,即 1 712 304 个棋盘
  • 列出 hand1 与棋盘 1 的所有 21 种组合,
  • 使用这 21 种组合搜索ranked_hands 字典并返回可能的最佳结果(21 种组合,因为在德州扑克中,您可以使用手牌中的一张、两张或不使用一张牌,其中任何一张是 5 张公共(public)牌)
  • 对 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 中

    最佳答案

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

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

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

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

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

  • 而且,无论您选择哪种方法:
  • 由于您将其作为学习练习,我强烈建议您使用 不是 使用 itertools可能还有collections .虽然这些是非常方便的库,但它们也隐藏了算法设计的一些非常基本和重要的方面。如果这被分配为本科计算机科学类(class)的家庭作业,那么这些图书馆很可能不会被允许。 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...]
    

    关于python - 如何加快我的 Python 扑克手与手牌赢率计算器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59435354/

    相关文章:

    python - 为 Mac OS X 10.6.8 安装 Pygame

    python - Python中列表的内存管理

    r - 在 2 个向量中查找常见的 "subchains"

    algorithm - 如何更有效地从 n 个集合中找到满足给定条件的最小组合?

    java - 如何从卡片列表中生成所有唯一的卡片对?

    java - 为 N 名玩家生成一手牌(每手五张牌)

    python - 从 keras.layers import Dense——无法导入名称 'Dense'

    Python 项目仅使用项目中的第一个单词进行排序

    r - 与 data.table 连接的排列

    java - 设计扑克游戏的结构