【发布时间】:2011-04-19 06:30:39
【问题描述】:
这个问题乍看起来很简单,但实际上比看起来要复杂得多。这让我一时难过。
有 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
谁能提出一个更好的方法来生成不同的手,或者指出我在尝试中出错的地方?
【问题讨论】:
-
好问题,你需要它做什么?如果你想用它来计算一只手对二手的机会,你可以使用蒙特卡罗方法
-
我正在预先计算所有单挑对决。蒙特卡洛适用于没有足够计算能力的人。 ;)
-
这是一个非常有趣的问题。我现在没有时间玩它,但是我想到了一些可能有用也可能没用的想法。首先是从高到低工作——即每张牌的等级必须小于或等于前一张牌的等级(例如A-9-9-8-2)。其次,我相信第一张牌只能抽梅花,第二张牌只能抽梅花或方块,第三张牌只能抽黑桃。 (我对你的按位代码一无所知,所以你可能已经在做这些事情了。)
-
您是否关心手牌的重要排名或实际排列?例如,如果您的前两张牌的花色不同,那么您就不可能形成同花,并且可以忽略该子树其余部分的花色。我认为只有大约 7500 个通过仙人掌 Kev 排名的扑克牌。让我看看能不能找到链接。
-
@davidchambers 你说得对,按排名顺序生成它们是正确的——这是消除手排列的最简单方法。正如您所描述的,每张新牌的有效套装确实增加了,除了有效套装取决于所有以前的牌 - 例如,如果前两张牌的花色相同,则第三张牌只有两种可能性。这是我最初使用 DFS 的方式,但我最终还是生成了同构的手。 ://
标签: python algorithm permutation combinatorics poker