1. 程式人生 > >Project Euler Problem 54 (C++和Python)

Project Euler Problem 54 (C++和Python)

Problem 54: Poker hands

In the card game poker, a hand consists of five cards and are ranked, from lowest to highest, in the following way:

  • High Card: Highest value card.
  • One Pair: Two cards of the same value.
  • Two Pairs: Two different pairs.
  • Three of a Kind: Three cards of the same value.
  • Straight: All cards are consecutive values.
  • Flush: All cards of the same suit.
  • Full House: Three of a kind and a pair.
  • Four of a Kind: Four cards of the same value.
  • Straight Flush: All cards are consecutive values of same suit.
  • Royal Flush: Ten, Jack, Queen, King, Ace, in same suit.

The cards are valued in the order:
2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, Queen, King, Ace.

If two players have the same ranked hands then the rank made up of the highest value wins; for example, a pair of eights beats a pair of fives (see example 1 below). But if two ranks tie, for example, both players have a pair of queens, then highest cards in each hand are compared (see example 4 below); if the highest cards tie then the next highest cards are compared, and so on.

Consider the following five hands dealt to two players:

Hand Player 1 Player 2 Winner
1 5H 5C 6S 7S KD Pair of Fives 2C 3S 8S 8D TD Pair of Eights Player 2
2 5D 8C 9S JS AC Highest card Ace 2C 5C 7D 8S QH Highest card Queen Player 1
3 2D 9C AS AH AC Three Aces 3D 6D 7D TD QD Flush with Diamonds Player 2
4 4D 6S 9H QH QC Pair of Queens Highest card Nine 3D 6D 7H QD QS Pair of Queens Highest card Seven Player 1
5 2H 2D 4C 4D 4S Full House With Three Fours 3C 3D 3S 9S 9D Full House with Three Threes Player 1

The file, poker.txt, contains one-thousand random hands dealt to two players. Each line of the file contains ten cards (separated by a single space): the first five are Player 1’s cards and the last five are Player 2’s cards. You can assume that all hands are valid (no invalid characters or repeated cards), each player’s hand is in no specific order, and in each hand there is a clear winner.

How many hands does Player 1 win?

1. 撲克牌遊戲的一些概念

讓我們來弄清楚撲克牌的一些基本概念。

撲克牌有四種花色(Suit):草(梅)花C,紅心H,黑桃S,方塊D。

在這裡插入圖片描述

牌面大小有13個級別(Rank),從最大A到最小的 2依次為:
在這裡插入圖片描述

每一手牌的力量(Hand Strength)從大到小有10種優先順序:
在這裡插入圖片描述

知道了Suit, Rank和 Hand Strength後,就很容易理解本題了。

2. 題目解析

撲克牌遊戲中,一手牌包括5張牌,牌的大小順序從低到高依次是2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, Queen, King, Ace,但Ace 也可以當作1,這是一個特例。我們可以定義每張牌的值從2到14。

每一手牌的力量從小到大依次是:無對(High Card),一對(One Pair) ,兩對(Two Pairs),三條(Three of a Kind),順子(Straight), 同花(Flush), 滿堂紅(Full House), 四條(Four of a Kind), 同花順(Straight Flush), 大同花順(Royal Flush)。我們可以定義優先順序從1到10。

兩個對手按照他們的手牌的力量(Hand Strength)比較他們的Rank,確定Player1 還是 Player 2誰獲勝。

五個例子說明了兩個人的手牌比較的過程和結果。

檔案poker.txt有1000手牌,計算出第一個選手(人)的牌贏得多少手牌?

3. 解題思路

如果我們逐個判斷每一手牌符合那個規則,那我們可能最多需要判斷10次才能確認每一手牌的rank value, 因為我們需要從大到小逐次判斷。如果這樣做的話,很可能要寫10個函式來判斷Hand Strength每一種情況。

我們能不能找到一種通用的方法來量化每一手牌的力量呢?如果有的話,就很方便了,事實上,我們可以通過每一手牌的力量的順序(score)和牌面的大小(card value) 兩個維度來確認rank values,最後根據rank values 就可以判斷兩個人的手牌大小了。

例如:

C++ 程式碼(開啟編譯開關PE0050_DEBUG)後 列印輸出的三個例子:

(“5D”, “8C”, “9S”, “JS”, “AC”) Rank: 1, (14, 11, 9, 8, 5)
(“2C”, “5C”, “7D”, “8S”, “QH”) Rank: 1, (12, 8, 7, 5, 2)

第一個例子,score 相同都是1, 符合High Card規則,然後,我們根據牌面大小列表 (14, 11, 9, 8, 5)和(12, 8, 7, 5, 2), 很容易知道 Player 2 獲勝。Rank的值是類PlayerHandRank。

(“5H”, “5C”, “6S”, “7S”, “KD”) Rank: 2, (5, 13, 7, 6)
(“2C”, “3S”, “8S”, “8D”, “TD”) Rank: 2, (8, 10, 3, 2)

第二個例子,score 相同都是2, 符合One Pair規則,然後,我們根據牌面大小列表 (5, 13, 7, 6)和(8, 10, 3, 2), 知道一對8比一對5大,很容易知道 Player 2 獲勝。

(“2D”, “9C”, “AS”, “AH”, “AC”) Rank: 4, (14, 9, 2)
(“3D”, “6D”, “7D”, “TD”, “QD”) Rank: 6, (12, 10, 7, 6, 3)

第三個例子,Player1 手牌的score 是4,Player2 手牌的score 是6, 符合Hand Strength 排序規則,然後很容易判斷同花(Flush)大於三條(Three of a Kind),知道 Player 2 獲勝。

Python 列印輸出的兩個例子:

[‘QS’, ‘QD’, ‘AC’, ‘AD’, ‘4C’] Rank: [2, (14, 12, 4)]
[‘6S’, ‘2D’, ‘AS’, ‘3H’, ‘KC’] Rank: [0, (14, 13, 6, 3, 2)]

第一個例子, Player 1獲勝。Rank的值儲存在一個tuple裡。

[‘4C’, ‘7C’, ‘3C’, ‘TD’, ‘QS’] Rank: [0, (12, 10, 7, 4, 3)]
[‘9C’, ‘KC’, ‘AS’, ‘8D’, ‘AD’] Rank: [1, (14, 13, 9, 8)]

第二個例子, Player 2獲勝。

4. C++程式設計

4.1 Class Diagram (類圖)

在這裡插入圖片描述

4.2 類 PlayerHandRank

我們把一手牌Hand的資訊儲存在類 PlayerHandRank裡,在類裡,我們定義了幾個資料成員,還定義了操作符 operator > , 最後比較player1Rank和player2Rank,如果為true,那麼Player1 win。

4.3 類 CardValue

我們很容易知道要把一手牌通過map來解析,就可以知道牌面大小和每張牌出現的次數,但map解析後的結果需要排序,排序的規則:牌出現的次數m_card_frequency 從大到小降序排列,牌面大小從高到低排序。這個排序通過定義operator > 來實現。

4.4 類 PE0054

我們定義了三個public的成員函式:

  • 建構函式 PE0054()來初始化成員資料 m_cardOrder_mp和m_straights_vec。
  • run_unit_test() 來執行單元測試用例。
  • getPlayer1WinHandsInPokerFile()用來獲得檔案poker.txt裡的所有1000手牌,得到player1獲勝的數。

我們還定義了private的資料成員和方法:

  • m_royal_flush_vec,初始化大同花順的向量,用來判斷大同花順。
  • m_cardOrder_mp,把牌面字元 ‘2’,‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’, ‘T’, ‘J’, ‘Q’, ‘K’, ‘A’ 和2, 3,4,5,6,7,8,9,10,11,12,13,14關聯起來,便於以後比較牌面大小。
  • m_ranks_vec,定義了幾種牌面出現的次數的模式,根據這種模式,getHandRankScore()根據索引值得到score。
  • m_straights_vec,列舉所有可能的straight的模式,checkHandStraight()會用到。
  • getPlayerHandRankValues(),計算每手牌的rank values,返回型別是類 PlayerHandRank。
  • checkPlayer1Win(),根據player 1的hand rank和player 2的hand rank,如果結果是true, 判斷player 1獲勝。
  • parsePlayersHandPair(),把每一組手牌拆解為player1的手牌(hand)和player2的手牌(hand)
  • 行內函數 getHandRankScore(),初步計算一手牌的rank score。
  • 行內函數 checkHandStraight(), 判斷一手牌是否為順子(straight)。
  • 行內函數 checkHandFlush(),判斷一手牌是否為同花(flush)。
  • 行內函數 printVectorElements(), 列印向量成員,例如:(5,13,7,6)。

4.5 C++11特性

  • 在類PE0054的定義中,我們初始化了成員變數,例如:m_royal_flush_vec , m_ranks_vec 和 m_straights_vec :
   vector<int> m_royal_flush_vec = { 14, 13, 12, 11, 10 }; 

    vector<vector<int>> m_ranks_vec = {{1, 1, 1, 1, 1}, {2, 1, 1, 1}, {2, 2, 1},
                                       {3, 1, 1}, {}, {}, {3, 2}, {4, 1}};
    vector<vector<int>> m_straights_vec = { {14, 5, 4, 3, 2} };
  • 在函式getHandRankScore()和checkHandStraight()裡,我們使用了for 和 auto的簡化迴圈,例如:
        for (auto v : m_straights_vec)
        {
            if (hand.m_cards_value_vec == v)
            {
                return true;
            }
        }

4.6 編譯開關

  • UNIT_TEST, 預設關閉。如果開啟編譯開關,main()將呼叫執行pe0054.run_unit_test(),一些單元測試的用例將執行,關掉編譯開關,main()將呼叫執行函式pe0054.getPlayer1WinHandsInPokerFile(),從檔案 poker.txt裡讀取資料,並返回結果。
  • PE0054_DEBUG,預設關閉。如果開啟編譯開關,程式執行時的一些debug 資訊被列印輸出到螢幕,關掉編譯開關,沒有debug 資訊被列印輸出到螢幕。

5. Python程式設計

5.1 collections.Counter()

Counter(計數器)是對字典的補充,用於追蹤值的出現次數。
Counter是一個繼承了字典的類(Counter(dict)),注意其返回值是tuple.

5.2 zip(*)

zip() 函式用於將可迭代的物件作為引數,將物件中對應的元素打包成一個個元組,然後返回由這些元組組成的列表。如果各個迭代器的元素個數不一致,則返回列表長度與最短的物件相同,利用 * 號操作符,可以將元組解壓為列表。

5.3 getPlayerHandRank()

這個函式用來獲得每一手牌的rank values,結果儲存在一個tuple裡, 例如:[2, (14, 12, 4)]。

下面的程式碼是整個函式的核心程式碼,資訊量很大:

    score = [zip(*(sorted(((v, cards_value_dict[k]) \
      for k,v in collections.Counter(x[0] for x in hand).items()),reverse=True)))]

可以毫不誇張地說,這道題的C++和Python程式碼都很經典,無論是解題思路還是程式碼本身都值得反覆推敲,充分展現了程式設計之美。

C++ 11 程式碼

// PE0054.cpp has used C++ 11 features  
    
// #include "pch.h"  // this header file is required by MS Visual Studio 2017

#include <iostream>
#include <string>
#include <fstream>
#include <map>
#include <set>
#include <vector>
#include <iterator>
#include <algorithm>
#include <cassert>

using namespace std;
    
//#define UNIT_TEST
//#define PE0054_DEBUG

// 1. High Card: Highest value card.
// 2. One Pair : Two cards of the same value.
// 3. Two Pairs : Two different pairs.
// 4. Three of a Kind : Three cards of the same value.
// 5. Straight : All cards are consecutive values.
// 6. Flush : All cards of the same suit.
// 7. Full House : Three of a kind and a pair.
// 8. Four of a Kind : Four cards of the same value.
// 9. Straight Flush : All cards are consecutive values of same suit.
// 10. Royal Flush : Ten, Jack, Queen, King, Ace, in same suit.

class PlayerHandRank
{
public:
    int             m_rank_score = 1;      // 1..10
    vector<int>     m_cards_frequency_vec; // {1,1,1,1,1}, {2,1,1,1}, etc
    vector<int>     m_cards_value_vec;     // {2, 3, ..., 14}
    set<char>       m_cards_suit_s;        // {'S', 'H', 'C', 'D'}
    vector<string>  m_cards_name_vec;      // {"QS", "QD", "AC", "AD", "4C"}

    bool operator > (const PlayerHandRank& rank) const
    {
        if (m_rank_score == rank.m_rank_score)
        {
            return m_cards_value_vec > rank.m_cards_value_vec;
        }
        else
        {
            return m_rank_score > rank.m_rank_score;
        }
    }
};

class CardValue
{
public:
    int m_card_value;       // card value (m_cards_value_vec)
    int m_card_frequency;   // pair value (m_cards_frequency_vec)

    bool operator > (const CardValue& cv) const
    {
        if (m_card_frequency == cv.m_card_frequency)
        {
            return m_card_value > cv.m_card_value;

        }
        else
        {
            return m_card_frequency > cv.m_card_frequency;
        }
    }
};
    
class PE0054
{
public:
    PE0054();
    void run_unit_test();
    int  getPlayer1WinHandsInPokerFile();

private:
    vector<int> m_royal_flush_vec = { 14, 13, 12, 11, 10 }; 

    //map<char, int> m_cardOrder_mp = { {'2',2},{'3', 3},{'4', 4},{'5', 5},{'6', 6},
    //{'7',7}, {'8', 8},{'9', 9},{'T',10}, {'J', 11},{'Q', 12},{'K', 13},{'A', 14} };
    map<char, int> m_cardOrder_mp;
    
    vector<vector<int>> m_ranks_vec = {{1, 1, 1, 1, 1}, {2, 1, 1, 1}, {2, 2, 1},
                                       {3, 1, 1}, {}, {}, {3, 2}, {4, 1}};
    vector<vector<int>> m_straights_vec = { {14, 5, 4, 3, 2} };

    PlayerHandRank getPlayerHandRankValues(vector<string>& handCards_vec);

    bool checkPlayer1Win(PlayerHandRank& hand1, PlayerHandRank& hand2);

    void parsePlayersHandPair (const string & hand_pair,
                              vector<string>& player1hand_cards,
                              vector<string>& player2hand_cards);

    int  getHandRankScore(PlayerHandRank &hand)
    {
        int score = 1;
        for (auto v : m_ranks_vec)
        {
            if (hand.m_cards_frequency_vec == v)
            {
                break;
            }
            else
            {
                score++;
            }
        }
        return score;
    }

    bool checkHandStraight(PlayerHandRank &hand)
    {
        for (auto v : m_straights_vec)
        {
            if (hand.m_cards_value_vec == v)
            {
                return true;
            }
        }
        return false;
    }

    bool checkHandFlush(PlayerHandRank &hand)
    {
        return (1 == hand.m_cards_suit_s.size());
    }

    void printVectorElements(vector<int> &vi)
    {
        int size = vi.size();
        cout << "(";
        for (int i = 0; i < size - 1; i++)
        {
            cout << vi[i] << ", ";
        }
        cout << vi[size - 1] << ") ";

    }
 
    void printVectorElements(vector<string> &vs)
    {
        int size = vs.size();
        cout << "(";
        for (int i = 0; i < size - 1; i++)
        {
            cout << "\"" << vs[i] << "\", ";
        }
        cout << "\"" << vs[size - 1] << "\")  ";
    }
};

PE0054::PE0054()
{
    string cardsName = "23456789TJQKA";
    for (int i = 2; i < 15; i++)
    {
        char c = cardsName[i - 2];
        m_cardOrder_mp[c] = i;
    }

    vector<int> sv(5);
    for (int i = m_cardOrder_mp['A']; i > m_cardOrder_mp['5']; i--)
    {
        // sv = {i, i-1, i-2, i-3, i-4}
        for (int j = 0; j < 5; j++)
        {
            sv[j] = i - j;
        }
        m_straights_vec.push_back(sv);
    }
}

void PE0054::parsePlayersHandPair (const string & hand_pair, 
                                   vector<string>& player1_cards, 
                                   vector<string>& player2_cards)
{
    string s(2, '0');

    for (unsigned int k = 0, j = 0; k < hand_pair.size(); k++)
    {
        if (hand_pair[k] != ' ')
        {
            if (j < 5)
            {
                s[0] = hand_pair[k];
                s[1] = hand_pair[k+1];
                player1_cards[j] = s;
            }
            else
            {
                s[0] = hand_pair[k];
                s[1] = hand_pair[k+1];
                player2_cards[j-5] = s;
            }
            j++;
            k++;
        }
    }
}
        
PlayerHandRank PE0054::getPlayerHandRankValues(vector<string>& handCards_vec)
{
    PlayerHandRank PlayerHandRank;
    map<char, int> cardsValue_mp; 
    char c;
 
    PlayerHandRank.m_cards_name_vec = handCards_vec;

    // "ASKD3DJD8H", 
    // cardsValue_mp = { {'A', 1}, {'K', 1}, {'3', 1}, {'J', 1}, {'8', 1} };
    // PlayerHandRank.m_cards_suit_s = {'D', 'H', 'S'}
    for (auto& v : handCards_vec)
    {
        c = v[0];
        cardsValue_mp[c]++;

        c = v[1];
        PlayerHandRank.m_cards_suit_s.insert(c);
    }
 
    map<char, int>::iterator iter = cardsValue_mp.begin();

    vector<CardValue> cv_vec;
    CardValue cv;

    while (iter != cardsValue_mp.end())
    {
        cv.m_card_value     = m_cardOrder_mp[iter->first];
        cv.m_card_frequency = iter->second;
        cv_vec.push_back(cv);

        iter++;
    }

    sort(cv_vec.begin(), cv_vec.end(), greater<CardValue>());

    for (auto& x : cv_vec)
    {
        PlayerHandRank.m_cards_frequency_vec.push_back(x.m_card_frequency);
        PlayerHandRank.m_cards_value_vec.push_back(x.m_card_value);
    }

    int score = getHandRankScore(PlayerHandRank);

    if (true == checkHandStraight(PlayerHandRank))
    {
        score = 5;           // straight 
    }

    if (true == checkHandFlush(PlayerHandRank))
    {
        if (5 == score)
        {
            score = 9;       // straight flush
        }
        else
        {
            score = 6;       // flush
        }

        if (PlayerHandRank.m_cards_value_vec == m_royal_flush_vec)
        {
            score = 10;      // Royal Flush 
        }
    }
    
    PlayerHandRank.m_rank_score = score;

    return PlayerHandRank;
}
    
bool PE0054::checkPlayer1Win (PlayerHandRank& player1Rank, 
                              PlayerHandRank& player2Rank)
{   
#ifdef PE0054_DEBUG
    printVectorElements(player1Rank.m_cards_name_vec);
    cout << "Rank: " << player1Rank.m_rank_score << ", ";
    printVectorElements(player1Rank.m_cards_value_vec);
    cout << endl;
 
    printVectorElements(player2Rank.m_cards_name_vec);
    cout << "Rank: " << player2Rank.m_rank_score << ", ";
    printVectorElements(player2Rank.m_cards_value_vec);
    cout << endl;
#endif
    
    return player1Rank > player2Rank;
}
    
void PE0054::run_unit_test()
{
    struct _TestCase
    {
        string hand;
        bool   player1_win;
    } TestCases [] = {
        {"5H 5C 6S 7S KD 2C 3S 8S 8D TD",  false},
        {"5D 8C 9S JS AC 2C 5C 7D 8S QH",  true},
        {"2D 9C AS AH AC 3D 6D 7D TD QD",  false},
        {"4D 6S 9H QH QC 3D 6D 7H QD QS",  true},
        {"2H 2D 4C 4D 4S 3C 3D 3S 9S 9D",  true},
        {"6H 4H 5C 3H 2H 3S QH 5S 6S AS",  true},
        {"TS 8H 9S 6S 7S QH 3C AH 7H 8C",  true},
        {"6H 5D 7S 5H 9C 9H JH 8S TH 7H",  false}
            
           

相關推薦

Project Euler Problem 54 (C++Python)

Problem 54: Poker hands In the card game poker, a hand consists of five cards and are ranked, from lowest to highest, in the following way:

Project Euler Problem 60 (C++Python)

Problem 60 : Prime pair sets The primes 3, 7, 109, and 673, are quite remarkable. By taking any two primes and concatenating them in any order

Project Euler Problem 59 (C++Python)

Problem 59 : XOR decryption Each character on a computer is assigned a unique code and the preferred standard is ASCII (American Standard Code

Project Euler Problem 58 (C++Python)

Problem 58 : Spiral primes Starting with 1 and spiralling anticlockwise in the following way, a square spiral with side length 7 is formed.

Project Euler Problem 57 (C++Python)

Problem 57 : Square root convergents It is possible to show that the square root of two can be expressed as an infinite continued fraction.

Project Euler Problem 56 (C++Python)

Problem 56 : Powerful digit sum A googol (10100) is a massive number: one followed by one-hundred zeros; 100100 is almost unimaginably large:

Project Euler Problem 53 (C++Python)

Problem 53: Combinatoric selections There are exactly ten ways of selecting three from five, 12345: 123, 124, 125, 134, 135, 145, 234, 235,

Project Euler Problem 52 (C++Python)

Problem 52: Permuted multiples It can be seen that the number, 125874, and its double, 251748, contain exactly the same digits, but in a diffe

Project Euler Poblem 55 (C++Python)

Problem 55 : Lychrel numbers If we take 47, reverse and add, 47 + 74 = 121, which is palindromic. Not all numbers produce palindromes so qui

[Perl 6][Project Euler] Problem 9 - Special Pythagorean triplet

spc tid int exists post auto gsp none style [Perl 6][Project Euler] Problem 9 - Special Pythagorean triplet Description A Pythagorea

旋轉數組的最小數字(C++ Python 實現)

ram 兩個 requires images red 輸入 off internet iat (說明:本博客中的題目、題目詳細說明及參考代碼均摘自 “何海濤《劍指Offer:名企面試官精講典型編程題》2012年”) 題目   把一個數組最開始的若幹個元素搬到數組的末尾,我們

數值的整數次方(C++ Python 實現)

n-n style elif function program ava right 直接 including (說明:本博客中的題目、題目詳細說明及參考代碼均摘自 “何海濤《劍指Offer:名企面試官精講典型編程題》2012年”) 題目   實現函數 double Powe

MxNet C++python環境配置

direct nbsp opencv eas gpo rect 1.0 tor enc MxNet C++和python環境配置 安裝文件: 1、為了與python已經安裝好的版本一致,在這個網站下載mxnet 1.0.0的源碼 https://github.com/apa

傳遞命令行參數示例代碼 (C Python)

font com 因此 char* tac 示例 std erl __name__ C語言 在 C 語言中, 使用 main 函數的輸入參數 argc 和 argv 傳入命令行參數. argc 為 int 類型, 表示傳入命令行參數的個數 (argument count

[轉] C++ python之間的互相呼叫

轉載自:https://www.cnblogs.com/apexchu/p/5015961.html   一、Python呼叫C/C++ 1、Python呼叫C動態連結庫         Python呼叫C庫比較簡單,不經

Project Euler Problem 46

Problem 46 : Goldbach’s other conjecture It was proposed by Christian Goldbach that every odd composite number can be written as the sum of a

Project Euler Problem 45

Problem 45 : Triangular, pentagonal, and hexagonal Triangle, pentagonal, and hexagonal numbers are generated by the following formulae: Tria

Project Euler Problem 44

Problem 44 : Pentagon numbers Pentagonal numbers are generated by the formula, Pn=n(3n−1)/2. The first ten pentagonal numbers are: 1, 5, 12,

Project Euler Problem 43

Problem 43 : Sub-string divisibility The number, 1406357289, is a 0 to 9 pandigital number because it is made up of each of the digits 0 to 9

Project Euler Problem 42

Problem 42 : Coded triangle numbers The nth term of the sequence of triangle numbers is given by, tn =½n(n+1); so the first ten triangle numbe