生成所有5张纸牌扑克手

问题描述 投票:38回答:13

乍看之下,这个问题听起来很简单,但事实却比看起来复杂得多。我现在为此感到难过。

有52c5 = 2,598,960种方法可从52张卡片组中选择5张卡片。但是,由于西装在扑克中是可以互换的,因此其中许多是等效的-手2H 2C 3H 3S 4D等效于2D 2S 3D 3C 4H-只需换一下西装即可。根据wikipedia,一旦您考虑了衣服的可能重新着色,就会有134,459张不同的5张牌。

问题是,我们如何有效地产生所有这些可能的手?我不想产生所有手牌,然后消除重复,因为我想将此问题应用于更多的牌,并且手牌数量过多以评估失控的快速螺旋。我目前的尝试集中在以下方面:先生成深度优先,并跟踪当前生成的卡,以确定对下一张卡有效的西服和等级,或者广度优先,生成所有可能的下一张卡,然后通过转换每张卡来消除重复通过重新着色将手移到“规范”版本。这是我尝试使用Python广度优先的解决方案:

# A card is represented by an integer. The low 2 bits represent the suit, while
# the remainder represent the rank.
suits = 'CDHS'
ranks = '23456789TJQKA'

def make_canonical(hand):
  suit_map = [None] * 4
  next_suit = 0
  for i in range(len(hand)):
    suit = hand[i] & 3
    if suit_map[suit] is None:
      suit_map[suit] = next_suit
      next_suit += 1
    hand[i] = hand[i] & ~3 | suit_map[suit]
  return hand

def expand_hand(hand, min_card):
  used_map = 0
  for card in hand:
    used_map |= 1 << card

  hands = set()
  for card in range(min_card, 52):
    if (1 << card) & used_map:
      continue
    new_hand = list(hand)
    new_hand.append(card)
    make_canonical(new_hand)
    hands.add(tuple(new_hand))
  return hands

def expand_hands(hands, num_cards):
  for i in range(num_cards):
    new_hands = set()
    for j, hand in enumerate(hands):
      min_card = hand[-1] + 1 if i > 0 else 0
      new_hands.update(expand_hand(hand, min_card))
    hands = new_hands
  return hands

不幸的是,这产生了太多的牌:

>>> len(expand_hands(set([()]), 5))
160537

任何人都可以提出一种更好的方法来产生独特的牌,或者指出我在尝试中出错的地方吗?

python algorithm permutation combinatorics poker
13个回答
18
投票

您的整体方法是正确的。我很确定问题出在您的make_canonical函数上。您可以尝试将num_cards设置为3或4的手打印出来,并寻找您错过的等效项。

我找到了一个,但可能还有更多:

# The inputs are equivalent and should return the same value
print make_canonical([8, 12 | 1]) # returns [8, 13]
print make_canonical([12, 8 | 1]) # returns [12, 9]

供参考,以下是我的解决方案(在查看您的解决方案之前已开发)。我使用深度优先搜索而不是宽度优先搜索。另外,我没有编写将手形转换为规范形式的函数,而是编写了一个函数以检查手形是否规范。如果不是规范,我将其跳过。我定义了等级=卡%13,西装=卡/13。这些差异都不重要。

import collections

def canonical(cards):
    """
    Rules for a canonical hand:
    1. The cards are in sorted order

    2. The i-th suit must have at least many cards as all later suits.  If a
       suit isn't present, it counts as having 0 cards.

    3. If two suits have the same number of cards, the ranks in the first suit
       must be lower or equal lexicographically (e.g., [1, 3] <= [2, 4]).

    4. Must be a valid hand (no duplicate cards)
    """

    if sorted(cards) != cards:
        return False
    by_suits = collections.defaultdict(list)
    for suit in range(0, 52, 13):
        by_suits[suit] = [card%13 for card in cards if suit <= card < suit+13]
        if len(set(by_suits[suit])) != len(by_suits[suit]):
            return False
    for suit in range(13, 52, 13):
        suit1 = by_suits[suit-13]
        suit2 = by_suits[suit]
        if not suit2: continue
        if len(suit1) < len(suit2):
            return False
        if len(suit1) == len(suit2) and suit1 > suit2:
            return False
    return True

def deal_cards(permutations, n, cards):
    if len(cards) == n:
        permutations.append(list(cards))
        return
    start = 0
    if cards:
        start = max(cards) + 1
    for card in range(start, 52):
        cards.append(card)
        if canonical(cards):
            deal_cards(permutations, n, cards)
        del cards[-1]

def generate_permutations(n):
    permutations = []
    deal_cards(permutations, n, [])
    return permutations

for cards in generate_permutations(5):
    print cards

它生成正确数量的排列:

Cashew:~/$ python2.6 /tmp/cards.py | wc
134459

0
投票

在这里看看:


0
投票

[如果您只对导致不同手牌排名的手感兴趣,实际上只需要考虑7462个不同的手类(请参阅Wikipedia


0
投票

机会是,您真的想产生非同等意义上的不同手数。在那种情况下,根据维基百科的文章,有7462种可能的牌。这是一个将全部枚举的python代码段。


0
投票

[考虑到已经抽出一些牌,当您考虑完成牌局时,这个问题会更加严重


3
投票

这里是一个使用numpy并生成规范交易及其多样性的Python解决方案。我使用Python的itertools模块创建了4套西服的全部24种可能排列,然后遍历所有2,598,960种5张牌交易。每笔交易仅需5行就可以排列并转换为规范ID。这是非常快的,因为循环仅经历10次迭代才能覆盖所有事务,并且仅需要管理内存需求即可。除了使用itertools.combinations以外,所有繁重的工作都可以在numpy中高效完成。可惜这不是直接在numpy中支持的。

import numpy as np
import itertools

# all 24 permutations of 4 items
s4 = np.fromiter(itertools.permutations(range(4)), dtype='i,i,i,i').view('i').reshape(-1,4)

c_52_5 = 2598960 # = binomial(52,5) : the number of 5-card deals in ascending card-value order
block_n = c_52_5/10
def all5CardDeals():
    '''iterate over all possible 5-card deals in 10 blocks of 259896 deals each'''
    combos = itertools.combinations(range(52),5)
    for i in range(0, c_52_5, block_n):
        yield np.fromiter(combos, dtype='i,i,i,i,i', count=block_n).view('i').reshape(-1,5)

canon_id = np.empty(c_52_5, dtype='i')
# process all possible deals block-wise.
for i, block in enumerate(all5CardDeals()):
    rank, suit = block/4, block%4     # extract the rank and suit of each card
    d = rank[None,...]*4 + s4[:,suit] # generate all 24 permutations of the suits
    d.sort(2)                         # re-sort into ascending card-value order
    # convert each deal into a unique integer id
    deal_id = d[...,0]+52*(d[...,1]+52*(d[...,2]+52*(d[...,3]+52*d[...,4])))
    # arbitrarily select the smallest such id as the canonical one 
    canon_id[i*block_n:(i+1)*block_n] = deal_id.min(0)
# find the unique canonical deal ids and the index into this list for each enumerated hand
unique_id, indices = np.unique(canon_id, return_inverse=True)
print len(unique_id) # = 134459
multiplicity = np.bincount(indices)
print multiplicity.sum() # = 2598960 = c_52_5

2
投票

您的问题听起来很有趣,所以我简单地尝试通过以某种方式循环所有可能的手来实现它。我没有详细查看您的代码,但看来我的实现与您的实现完全不同。猜猜我的脚本找到了多少手:160537

  • 例如,我的手总是整理好2 3 4 4 D
  • 如果有两张相等的卡片,则颜色也会排序(颜色仅称为0、1、2、3)
  • 第一张卡片的颜色始终为0,第二张卡片的颜色始终为0或1
  • 一张卡片只能具有前一张卡片或下一个较大数字的颜色,例如如果卡片1 + 2的颜色为0,则卡片3的颜色只能为0或1]

您确定维基百科上的数字正确吗?

count = 0
for a1 in range(13):
    c1 = 0
    for a2 in range(a1, 13):
        for c2 in range(2):
            if a1==a2 and c1==c2:
                continue
            nc3 = 2 if c1==c2 else 3
            for a3 in range(a2, 13):
                for c3 in range(nc3):
                    if (a1==a3 and c1>=c3) or (a2==a3 and c2>=c3):
                        continue
                    nc4 = nc3+1 if c3==nc3-1 else nc3
                    for a4 in range(a3, 13):
                        for c4 in range(nc4):
                            if (a1==a4 and c1>=c4) or (a2==a4 and c2>=c4) or (a3==a4 and c3>=c4):
                                continue
                            nc5 = nc4+1 if (c4==nc4-1 and nc4!=4) else nc4
                            for a5 in range(a4, 13):
                                for c5 in range(nc5):
                                    if (a1==a5 and c1>=c5) or (a2>=a5 and c2>=c5) or (a3==a5 and c3>=c5) or (a4==a5 and c4>=c5):
                                        continue
                                    #print([(a1,c1),(a2,c2),(a3,c3),(a4,c4),(a5,c5)])
                                    count += 1
print("result: ",count)

1
投票

我不是扑克玩家,所以关于手的优先次序的细节超出了我。但是似乎问题在于,当您应该遍历“独特的扑克手”的空间时,您会通过从甲板上生成套数来遍历“ 5张纸牌组”的空间。

不同手的空间将需要新的语法。重要的是准确地捕获与手优先级相关的信息。例如,只有4只手是皇家同花顺,因此这些手可以描述为符号“ RF”加上西服标识,例如俱乐部中皇家同花顺的“ RFC”。 10高的心脏潮红可能是“ FLH10”(不确定是否有其他优先级的潮红特征,但是我想这就是您需要知道的全部信息)。如果我不理解您的最初问题说明,则“ 2C 2S AH 10C 5D”牌会更长一些,例如“ PR2 A 10 5”。

一旦定义了不重复手的语法,就可以将其表示为正则表达式,这将告诉您如何生成不重复手的整个空间。听起来很有趣!


1
投票

您可以简单地给所有手分配一个规范的值(从A到K),然后根据抽象西服字母的首次出现顺序为其分配该顺序。


1
投票

初始输入:


1
投票

这是一个简单而直接的算法,用于基于西装排列将手减为标准的手。


0
投票

Pokersource。如果考虑考虑已经绘制了一些卡片,则问题变得更糟。


0
投票

生成5张手牌的等效类并非易事。当我需要此功能时,通常会使用http://www.vpgenius.com/网页。在http://www.vpgenius.com/video-poker/games/,您可以选择所需的各种扑克游戏,在“编程选项卡”中,有一个“独特的花样样式”部分。因此,仅将其复制并加载到程序中可能比尝试生成自己的要容易。

© www.soinside.com 2019 - 2024. All rights reserved.