Project Euler 54: Poker hands

在紙牌遊戲中,一手包含五張牌而且每一手都有本身的排序,從低到高的順序以下:python

  • 大牌:牌面數字最大
  • 一對:兩張牌有一樣的數字
  • 兩對:兩個不一樣的一對
  • 三條:三張牌有一樣的數字
  • 順子:全部五張牌的數字是連續的
  • 同花:全部五張牌有一樣的花色
  • 船牌:三張一樣數字的牌加一個一對
  • 四條:四張牌有一樣的數字
  • 同花順:牌面數字連續且有一樣的花色
  • 皇家同花順:由\(10,J,Q,K,A\)五張牌構成的同花順

十三張牌根據牌面數字從小到大排序爲:\(2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K, A\)數據結構

若是兩個玩家所持的一手牌排序相同,那麼牌面數字較大的獲勝。好比,一對八要大於一對五(見下面例一)。可是若是兩手牌排序打平,好比兩個玩家都有一對\(Q\),那麼比較剩下的牌中的最大者(見下面例四)。若是這張牌仍然打平,則比較其它剩下的牌中的最大者,依次類推。ui

考慮兩個玩家所持的下面五手牌,觀察他們的勝負狀況:spa

img

文本文件poker.txt中包含一千條隨機生成的兩個玩家的牌面數據,每一行包含十張牌(用一個空格分開),前五張牌是第一個玩家的,後五張牌是第二個玩家的。你能夠認爲全部牌都是有效的,每一個玩家所持的牌沒有特定的順序,而且在每一手牌中總有一個肯定的獲勝者。求在這一千手牌中玩家一獲勝了多少次?.net

分析:這是咱們到目前爲止看到的文字最多的題目,並且我爲了解決這個題目所寫的代碼也是到目前行數最多的。和之前的題目不一樣,這道題目的難點並不在於其中的數學原理,而是要對這個遊戲的規則自己有清楚的瞭解,而且可以把這些規劃用代碼表示出來。題目自己介紹規則的介紹不是很清楚,我建議你們參考這篇維基百科,裏面對這個遊戲的規則有更透徹清晰的介紹。總結起來遊戲的規則有這麼幾條:第一,每一手牌有九個可能的排序(皇家同花順就是同花順,因此不用單獨考慮),排序高的牌大於排序低的牌;第二,若是兩手牌的排序相同,則須要根據這一手牌的不一樣類型來進行排序,總結起來也有兩種狀況:(1)若是類型中不涉及對子以及各類條牌,也就是同花順、同花、順子以及大牌這四種類型時,只須要對牌面數字造成的列表從大到小排序,而後比較兩個列表便可。由於比較列表時,它會從第一個數字陸續向後比較,所以和這四種類型的牌的比較方式相同;(2)剩下五種涉及到對子和條牌的類型,也即四條、三條、船牌、二對和一對,則須要首先按條或者對分組,而後對條或者對中的牌面數字分別比較大小,這裏能夠用到python中Counter這個數據結構。code

因爲解題過程涉及大量的邏輯判斷,爲了讓代碼結構更加清晰,我使用了面向對象的方式來組織代碼。我編寫了兩個類:第一類是紙牌類,它有兩個屬性,分別是花色與牌面數字,爲了讓以後的比較更加方便,我把\(T,J,Q,K,A\)五個牌面分別映射至對應的數字。第二個類表示五張牌造成的一手,這個類有八個屬性,分別表示五張牌的牌面數字、五張牌的花色、五張牌牌面數字的分組統計、第一個分組的牌面數字、第二個分組的牌面數字、五張牌花色的類型數量、五張牌面數字從大到小排序的列表以及五張牌從大到小排序後,先後兩個數字之差構成的集合。此外,這個類有兩個方法:第一個方法用來判斷這一手牌的類型,返回每一手牌的類型名稱以及類型排序;第二個方法用來比較這一手牌和另外一手牌的大小,使用的是咱們前面總結的比較各手之間大小的規則。對象

最後,咱們從文本文件中導入數據,把每一行數據拆分爲第一個玩家和第二個玩家,並記錄第一個玩家獲勝的次數,最後返回這個次數即爲題目所求。代碼以下:blog

# time cost = 42.1 ms ± 157 µs

from collections import Counter

class Card:
    def __init__(self,vs):
        d = {'T':10,'J':11,'Q':12,'K':13,'A':14}
        self.s = vs[1]        
        if vs[0] not in set('TJQKA'):
            self.v = int(vs[0])
        else:
            self.v = d[vs[0]]
        
class Hand:
    def __init__(self,cards):
        self.values = [x.v for x in cards]
        self.suits = [x.s for x in cards]
        self.value_counter = Counter(self.values).most_common()
        self.fc = self.value_counter[0][1]
        self.sc = self.value_counter[1][1]
        self.suit_kind = len(set(self.suits))
        self.ranks = sorted([x.v for x in cards],reverse=True)
        self.diff = set([self.ranks[:-1][i]-self.ranks[1:][i] for i in range(4)])
    
    def categories(self):
        if self.suit_kind == 1 and self.diff == {1}:
            return ('Straight Flush',9)
        elif self.suit_kind == 1:
            return ('Flush',6)
        elif self.diff == {1}:
            return ('Straight',5)
        elif self.fc == 4:
            return ('Four of a Kind',8)
        elif self.fc == 3 and self.sc == 2:
            return ('Full House',7)
        elif self.fc == 3 and self.sc == 1:
            return ('Three of a Kind',4)
        elif self.fc == 2 and self.sc == 2:
            return ('Two Pairs',3)
        elif len(self.value_counter) == 4 and self.fc == 2:
            return ('One Pair',2)
        else:
            return ('High Card',1)
    
    def is_winner(self,hand):
        if self.categories()[1] > hand.categories()[1]:
            return True
        elif self.categories()[1] < hand.categories()[1]:
            return False
        elif self.categories()[1] in [8,7,4,3,2]:
            return self.value_counter > hand.value_counter
        else:
            return self.ranks > hand.ranks

def main():
    count = 0
    with open('data/ep54.txt') as f:
        hands = [line.split() for line in f]
    for hand in hands:
        p1_cards = [Card(x) for x in hand[:5]]
        p2_cards = [Card(x) for x in hand[5:]]
        p1_hand,p2_hand = Hand(p1_cards),Hand(p2_cards)
        if p1_hand.is_winner(p2_hand):
            count += 1
    return count
相關文章
相關標籤/搜索