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?
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.
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.
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:
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.
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.
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.