生成所有 5 张牌扑克牌
这个问题乍一看很简单,但事实证明比看起来要复杂得多。一时让我难住了。
从 52 张牌中选择 5 张牌有 52c5 = 2,598,960 种方法。然而,由于扑克中的花色可以互换,因此其中许多花色是等效的 - 手牌 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
任何人都可以建议一种更好的方法来生成不同的手,或者指出我在尝试中出错的地方吗?
This problem sounds simple at first glance, but turns out to be a lot more complicated than it seems. It's got me stumped for the moment.
There are 52c5 = 2,598,960 ways to choose 5 cards from a 52 card deck. However, since suits are interchangeable in poker, many of these are equivalent - the hand 2H 2C 3H 3S 4D is equivalent to 2D 2S 3D 3C 4H - simply swap the suits around. According to wikipedia, there are 134,459 distinct 5 card hands once you account for possible suit recolorings.
The question is, how do we efficiently generate all these possible hands? I don't want to generate all hands, then eliminate duplicates, as I want to apply the problem to larger numbers of cards, and the number of hands to evaluate fast spirals out of control. My current attempts have centered around either generating depth-first, and keeping track of the currently generated cards to determine what suits and ranks are valid for the next card, or breadth-first, generating all possible next cards, then removing duplicates by converting each hand to a 'canonical' version by recoloring. Here's my attempt at a breadth-first solution, in 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
Unfortunately, this generates too many hands:
>>> len(expand_hands(set([()]), 5))
160537
Can anyone suggest a better way to generate just the distinct hands, or point out where I've gone wrong in my attempt?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(11)
您的总体方法是合理的。我很确定问题出在您的
make_canonical
函数上。您可以尝试打印 num_cards 设置为 3 或 4 的手牌,并查找您错过的等价项。我找到了一个,但可能还有更多:
作为参考,下面是我的解决方案(在查看您的解决方案之前开发)。我使用深度优先搜索而不是广度优先搜索。另外,我没有编写一个将手牌转换为规范形式的函数,而是编写了一个函数来检查手牌是否规范。如果它不规范,我会跳过它。我定义了等级 = 卡 % 13 和花色 = 卡 / 13。这些差异都不重要。
它生成正确数量的排列:
Your overall approach is sound. I'm pretty sure the problem lies with your
make_canonical
function. You can try printing out the hands with num_cards set to 3 or 4 and look for equivalencies that you've missed.I found one, but there may be more:
For reference, below is my solution (developed prior to looking at your solution). I used a depth-first search instead of a breadth-first search. Also, instead of writing a function to transform a hand to canonical form, I wrote a function to check if a hand is canonical. If it's not canonical, I skip it. I defined rank = card % 13 and suit = card / 13. None of those differences are important.
It generates the correct number of permutations:
这是一个 Python 解决方案,它使用 numpy 并生成规范交易及其多重性。我使用 Python 的 itertools 模块创建 4 种花色的所有 24 种可能的排列,然后迭代所有 2,598,960 种可能的 5 张牌交易。每笔交易只需 5 行即可排列并转换为规范 ID。它的速度相当快,因为循环仅经过 10 次迭代即可涵盖所有交易,并且仅需要管理内存需求。除了使用 itertools.combinations 之外,所有繁重的工作都在 numpy 中高效完成。遗憾的是,numpy 不直接支持这一点。
Here's a Python solution that makes use of numpy and generates the canonical deals as well as their multiplicity. I use Python's itertools module to create all 24 possible permutations of 4 suits and then to iterate over all 2,598,960 possible 5-card deals. Each deal is permuted and converted to a canonical id in just 5 lines. It's quite fast as the loop only goes through 10 iterations to cover all deals and is only needed to manage the memory requirements. All the heavy lifting is done efficiently in numpy except for the use of
itertools.combinations
. It's a shame this is not supportedly directly in numpy.你的问题听起来很有趣,所以我简单地尝试通过以排序的方式循环所有可能的手来实现它。我没有详细查看您的代码,但看来我的实现与您的有很大不同。猜猜我的脚本找到了多少手牌:160537
1当然,维基百科上的数字是正确的吗?
Your problem sounded interesting, so i simple tried to implements it by just looping over all possible hands in a sorted way. I've not looked at your code in details, but it seems my implementation is quite different from yours. Guess what count of hands my script found: 160537
Are you sure, the number on wikipedia is correct?
我不是扑克玩家,所以手牌优先顺序的细节超出了我的范围。但问题似乎在于,当您应该遍历“不同的扑克手”的空间时,您正在通过从牌组生成集合来遍历“5 张牌的集合”的空间。
不同手的空间将需要新的语法。重要的是准确捕获与手牌优先级相关的信息。例如,只有 4 手皇家同花顺,因此这些手牌可以被描述为符号“RF”加上花色指示符,就像俱乐部中皇家同花顺的“RFC”一样。 10 高心同花可能是“FLH10”(不确定同花是否还有其他优先特征,但我认为这就是您需要知道的全部)。如果我理解您最初的问题陈述,“2C 2S AH 10C 5D”的手牌将是一个更长的表达式,例如“PR2 A 10 5”。
一旦定义了不同手的语法,您就可以将其表达为正则表达式,这将告诉您如何生成不同手的整个空间。听起来很有趣!
I'm not a poker player, so the details of hand precedence are beyond me. But it seems like the problem is that you are traversing the space of "sets of 5 cards" by generating sets from the deck, when you should be traversing the space of "distinct poker hands".
The space of distinct hands will require a new grammar. The important thing is to capture exactly the information that is relevant to hand precedence. For example, there are only 4 hands that are royal flushes, so those hands can be described as the symbol "RF" plus a suit designator, like "RFC" for royal flush in clubs. A 10-high heart flush could be "FLH10" (not sure if there are other precedence characteristics of flushes, but I think that's all you need to know). A hand that is "2C 2S AH 10C 5D" would be a longer expression, something like "PR2 A 10 5" if I undestand your initial problem statement.
Once you have defined the grammar of distinct hands, you can express it as regular expressions and that will tell you how to generate the entire space of distinct hands. Sounds like fun!
您可以简单地为所有牌提供规范的值排序(A 到 K),然后根据它们在该顺序中首次出现的顺序分配抽象花色字母。
示例:JH 4C QD 9C 3D 将转换为 3a 4b 9b Jc Qa。
生成应该以动态编程的方式发挥最佳效果:
You could simply give all hands a canonical ordering of values (A to K), then assign abstract suit letters according to their order of first appearance in that order.
Example: JH 4C QD 9C 3D would convert to 3a 4b 9b Jc Qa.
Generation should work best as dynamic programming:
初始输入:
第 1 步:对于大于或等于所使用的最高等级的每个等级,将该等级中的所有花色设置为 0。您可以只检查较高的牌,因为较低的组合将由较低的起始点检查。
步骤 2:折叠到不同的行
步骤 3:返回确定与每个不同行匹配的第一个花色,并选择与不同行匹配的花色(由 * 标识)
现在显示等级 3 的重复
步骤 4:一旦有5 个单元格设置为 1,将可能的总花色抽象手数增加 1 并向上递归。
可能的花色抽象手牌总数为 134,459 手。这是我为了测试它而编写的代码:
希望它被分解得足够容易理解。
Initial input:
Step 1: for each rank greater than or equal the highest rank used, set all suits in that rank to 0. you can get away with only checking higher cards because lower combinations will be checked by the lower starting points.
Step 2: Collapse to distinct rows
Step 3: Climb back up determining first suit that match each distinct row, and choose the suits which match the distinct rows (identified by a *)
Now showing the repeat for rank 3
Step 4: Once there are 5 cells set to 1, increment the total possible suit abstracted hands count by 1 and recurse up.
The total number of suit abstracted hands possible is 134,459. This is the code I wrote to test it out:
Hopefully it is broken up enough to be easily understandable.
这是一种简单直接的算法,用于根据花色排列将手牌简化为规范手牌。
这就是 C++ 中的算法,带有一些隐含的 Suit 和 CardSet 类。请注意,return 语句通过连接位串来转换手形。
Here is a simple and straightforward algorithm for reducing hands to a canonical one based on suit permutatoins.
This is what the algorithm looks like in C++, with some implied Suit and CardSet classes. Note that the return statement converts the hand by concatenating the bitstrings.
生成 5 张牌的等价类并不是一件容易的事。
当我需要这个时,我通常使用 http://www.vpgenius.com/ 网页。在 http://www.vpgenius.com/video-poker/games/ 您可以选择您需要的扑克游戏种类,并且在“编程选项卡”中您有一个关于“独特花色图案”的部分。因此,只需复制它并加载到程序中可能比尝试生成自己的程序更容易。
Generating equivalence classes for 5 card hands is not an easy task.
When I need this I usually use the http://www.vpgenius.com/ webpage. At http://www.vpgenius.com/video-poker/games/ you can choose which variety of poker game you need, and in the "Programming tab" you have an section on "Unique Suit Patterns". So just copying that and loading into program might be easier than trying to generate your own.
看一下这里:
http://specialk-coding.blogspot.com/
http://code.google.com/p/specialkpokereval/
这些考虑的是 5 张牌(和 7 张牌) -牌手)作为整数,各个牌的总和,与花色无关。正是您所需要的。
这是用 Objective-C 和 Java 编写的快速排名 7 张牌和 5 张牌的方案的一部分。
Take a look here:
http://specialk-coding.blogspot.com/
http://code.google.com/p/specialkpokereval/
These regard a 5-card hand (and a 7-card hand) as an integer, the sum the individual cards, which is independent of the suit. Exactly what you need.
This is part of a scheme for quickly ranking 7- and 5-card hands, written in Objective-C and Java.
如果您只是对导致不同牌局排名的牌局感兴趣,则实际上只需考虑 7462 种不同的牌局类别(请参阅 维基百科)。
通过创建一个表格,其中包含每个类别及其伴随的多重性的示例,您可以非常快速地检查所有相关的手牌及其概率加权。也就是说,假设没有牌是已知的并且因此已经预先固定。
If you are just interested in hands that result in different hand rankings, there are actually only 7462 distinct hand classes that have to be considered (see Wikipedia).
By creating a table with an example for each class and their accompanying multiplicity you can check all relevant hands weighted with their probability quite fast. That is, assuming that no cards are known and therefore fixed beforehand already.
很可能您确实想要生成不同手牌的数量(从非等价的意义上来说)。在这种情况下,根据维基百科文章,可能有 7462 手牌。这是一个 python 代码片段,它将枚举所有这些。
逻辑很简单:每 5 组等级对应一只手;另外,如果所有的等级都不同,则可以通过使所有花色相匹配来形成另一种不同的手牌。
Chances are you really want to generate the number of distinct hands, in the sense of non-equivalent. In that case, according to the wikipedia article there are 7462 possible hands. Here is a python snippet that will enumerate them all.
The logic is simple: there is one hand for each 5-set of ranks; in addition, if all the ranks are distinct, another, different kind of hand can be formed by making all the suits match.