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?
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.
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!
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:
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?
Look at Pokersource. The problem gets even worse when you're considering completing hands given some cards already drawn.
The guy behind PokerStove did a great job in this direction, but the source is disclosed.
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.