Been meaning to write about them as I solve them because I've always been doing that in my head, so—here we go!
Project Euler 54 How many hands did player one win in the game of poker?
Ooo wow. I may have made this problem bigger/more complicated than it actually is.
Wanted to tackle this problem because I've always enjoyed card games, though my experience with poker is quite limited and rather unconventional.
Started out with how I wanted to represent a card: as a tuple (Rank, Suit); the Ace of Spades would be represented as (Ace, Spades). The dataset gives a round as a set of 10 cards, each card represented by 2 digits (one for the rank, one for the suit). Converting the data into my tuple–card format was easy—just did some exciting pattern matching. I'd imagine (see, I did some research on representing playing cards in haskell) that there was an easier/more succinct way of doing this, but I quite liked my method, as it seemed logical to me.
With representing cards taken care of I moved onto analyzing a hand to determine if it is a royal flush, a straight, etc. I believe I wrote two or three functions to take care of this, because I kept coming across better ways to handle this. The hardest hand to check for was the two pair, but we'll get there in a moment.
In poker, when two players tie, the winner is decided by whose hand contains the highest (relevant) card. Relevant meaning, if player 1 has a pair of Queens and player 2 has a pair of Jacks, player 1 would win because their relevant highest card, Queen, trumps player 2's Jack. If both players have a pair of Queens, then the highest relevant card is the highest card in their hands that is not a Queen. This meant that, when I analyzed a function, I would need to determine what kind of hand it was as well as make note/"take out"/keep track of the "tiebreaking" cards. These tiebreakers might be compared to each other (eg in the case where player 1 has a pair of Queens and player 2 has a pair of Jacks) or they would need to be removed from their respective player's hand so the remaining cards could be shot through some generic highestCard function that would spit out one card with the highest rank and thus settle a tie. I needed, I soon realized, to pass on the cards themselves. Thus my function analyzeHand went from having a type signature of Cards -> Hand to a type signature of Cards -> (Hand, [Card])
(Cards is to [Card] as String is to Char, as I'm sure you've figured out. Just decided to write in [Card] in that example to make it clearer that a list of one element might be passed on.)
I think Cards -> (Hand, Rank) would have worked as well—that is what I had used originally, actually, but I made an executive decision to change that and cannot remember for the life of me why that was. It'll come back to me, though, I know. (Maybe.) Will elaborate when it does.
Ohoho. Do you know why they call a straight flush a straight flush? I bet you can guess.
A lot of these hands are similar/related to each other (eg straight flush to straight & flush; one pair, four of a kind, etc.) which meant—yay!—I would get to reuse and recycle functions to analyze a hand. A hand was straight flush if straight hand && flush hand. I wrote a function called getpair (I've got to come up with better names, I know) that took a hand of cards and an integer n and gave back a list of cards from the hand consisting of n elements whose ranks were all the same. This function was used to pluck out four of a kind, three of a kind, and one pair from any given hand. (Nothing was plucked, of course, if the hand did not have four of a kind or whatever, and so the function returned a null list. If the returned list was not null then we knew that there was x of a kind in the hand.)
I decided it would be better to sacrifice consistency for clarity and it would be less of a headache as well, so I wrote a separate function to check if a hand has 2 pairs. It involved the getpair function as well as some shenanigans and unicorns.
twopair hand = let onepair = getpair hand 2
in getpair (hand \\ onepair) 2
After looking through the data set (always a good idea, I've learned!) I discovered that the hands were not given in ascending order, which really threw a wrench in my plans because I had relied on the cards being sorted when checking to see if a hand was straight. This, however, turned out to be an easier fix than I thought! I just added deriving (Ord) to my data Suit (already had it for Ranks, I am sure you can imagine why) and then let Data.List's sort function take care of sorting each player's cards. The only catch was to make sure to divide the data's 10 2-digit card representations into hands (the first five for player 1, the second half for player 2) before sorting the cards, elsewise everything would be jumbled up and completely, utterly, and totally wrong.
The first solution I got was 385, the second 384 (I made a lot of changes to my solution only to get an answer different by one!). The third solution looked pretty sketch but was, to my surprise, correct :]
No comments:
Post a Comment