【Project Euler】Problem 54: ポーカーの手

* 本記事は[ProjectEuler](https://projecteuler.net/about)の「100番以下の問題の説明は記載可能」という規定に基づいて回答のヒントが書かれていますので、自分である程度考えてみてから読まれることをお勧めします。

### 問題 54. ポーカーの手

原文 [Problem 54: Poker hands](https://projecteuler.net/problem=54)

**問題の要約：ファイルのポーカーの対戦結果を見てプレーヤー１の勝った数を求めよ**

ファイルに**5H5C6S7SKD 2C3S8S8DTD**のようにポーカーの対戦結果が1000組書かれているので以下のように勝敗判定を行うという問題です。

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

ファイルから読む部分は後の方に載せましたので、ここでは以下のように配列に既に格納されているといると考えます。

```
c5 = ['5C', 'QC', 'QH', 'AS', 'TS']
```

### 役の判定の手順
ここから役の判定に必要な情報を取り出します。まず数字を取り出して整数に変換します。エースは一番強いので14にします。

```
 ['5', 'Q', 'Q', 'A', 'T']　--> npart = [5, 12, 12, 14, 10]
```

次に**Counter**関数の**most_common()**メソッドを使って頻度の順番に並び替えます。12が2つあることが分かります。この頻度と数字の部分を分けて**nk**と**nn**とします。この**nk**で役の判定**nn**で役が同じとき数字の判定を行うという感じです。マークも同様にします。

```python
nmc = Counter(npart).most_common()
# [(12, 2), (5, 1), (14, 1), (10, 1)]
nk = [k for (n,k) in nmc]
# [2, 1, 1, 1]  --> One pair
nn = [n for (n,k) in nmc]
# [12, 5, 14, 10]
```

### メインの3つの関数

* **cardAttr :**  判定に必要な情報を取り出し
* **isStraight :** ストレートの判定
* **handCheck :** 役を判定して**[役番号, 判定用数字の優先度順の列]**を返します（例 [1, 12, 14, 10, 5], 1="One pair"）この大小比較で勝敗判定が行えます。

```python
from collections import Counter
import numpy as np

NO = ["?","?","2","3","4","5","6","7","8","9","T","J","Q","K","A"]
def cardAttr(c5):
  npart = list(map(NO.index,[s[0] for s in c5] )) # Number part 
  nmc = Counter(npart).most_common()              # most common
  spart = [s[1] for s in c5]                      # Suit part 
  return npart, [k for (n,k) in nmc], [n for (n,k) in nmc], [k for (s,k) in Counter(spart).most_common()]  # Suit part

def isStraight(snpart):  # if straight, return max number, else return 0 
  if (list(np.diff(snpart)).count(1))==4:  
    return snpart[-1]      #   return max number
  return 0

def handCheck(c5):
  npart, nk, nn, sk = cardAttr(c5)     # Card attributes 

  stmax = isStraight(sorted(npart))    # if straight, stmax: max number
  if stmax > 0 and sk == [5]:          # Straight Flush
    if stmax == 14: return [9]+[stmax] # Royal Flush
    else: [8]+[stmax]                  # Straight Flush
  if nk == [4, 1]: return [7]+nn       # Four of a Kind
  if nk == [3, 2]: return [6]+nn       # Full House
  if sk == [5]: return [5]+sorted(npart,reverse=True)  # Flush
  if stmax > 0: return [4]+[stmax]     # Straight
  if nk == [3, 1, 1]: return [3]+[nn[0]]+sorted(nn[1:], reverse=True)   # Three of a Kind
  if nk == [2, 2, 1]: return [2]+sorted(nn[:2], reverse=True)+[nn[2]]   # Two Pairs
  if nk == [2, 1, 1, 1]: return[1]+[nn[0]]+sorted(nn[1:], reverse=True) # One Pair
  return [0]+sorted(npart,reverse=True)                # High Card
```

### すべての結果の勝敗判定

**handCheck**の出力の配列はそのまま比較すれば勝敗判定が出来るのでプレーヤー１の勝ちの数を数えます。

```python 
print(f"Answer : {sum([handCheck(c[0])>handCheck(c[1]) for c in cards ])}")
```

### ファイから読みだして配列に格納するコード

```python
# show upload dialog
from google.colab import files
uploaded = files.upload()
#---- read words from file to name[]
f = open("p054_poker.txt")
handslist = f.readlines()
f.close()
count, cards = 0, []
for hands in handslist:
  h12 = hands.split()
  cards.append([h12[:5],h12[5:]])
print(cards[0])
```

（開発環境：Google Colab）