Project Euler 54「ポーカーハンド」

一旦書き上げて、後から綺麗で簡潔なコードに修正しようと思っていた時期が僕にもありました。

# Problem 54 「ポーカーハンド」
カードゲームのポーカーでは, 手札は5枚のカードからなりランク付けされている. 役を低い方から高い方へ順に並べると以下である.

**役無し(ハイカード)**: 一番値が大きいカード
**ワン・ペア**: 同じ値のカードが2枚
**ツー・ペア**: 2つの異なる値のペア
**スリーカード**: 同じ値のカードが3枚
**ストレート**: 5枚の連続する値のカード
**フラッシュ**: 全てのカードが同じスート (注: スートとはダイヤ・ハート・クラブ/スペードというカードの絵柄のこと)
**フルハウス**: スリーカードとペア
**フォーカード**: 同じ値のカードが4枚
**ストレートフラッシュ**: ストレートかつフラッシュ
**ロイヤルフラッシュ**: 同じスートの10, J, Q, K, A
ここでカードの値は小さい方から2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K, Aである. (訳注：データ中で10は'T'と表される)

もし2人のプレイヤーが同じ役の場合には, 役を構成する中で値が最も大きいカードによってランクが決まる: 例えば, 8のペアは5のペアより強い (下の例1を見よ). それでも同じランクの場合には (例えば, 両者ともQのペアの場合), 一番値が大きいカードによってランクが決まる (下の例4を見よ). 一番値が大きいカードが同じ場合には, 次に値が大きいカードが比べれられ, 以下同様にランクを決定する.

例:

|試合|プレイヤー1|プレイヤー2|勝者|
|:--:|:--:|:--:|:--:|
|1|5H 5C 6S 7S KD<br>5のペア|2C 3S 8S 8D TD<br>8のペア|プレイヤー2|
|2|5D 8C 9S JS AC<br>役無し, A|2C 5C 7D 8S QH<br>役無し, Q|プレイヤー1|
|3|2D 9C AS AH AC<br>Aのスリーカード|3D 6D 7D TD QD<br>ダイヤのフラッシュ|プレイヤー2|
|4|4D 6S 9H QH QC<br>Qのペア, 9|3D 6D 7H QD QS<br>Qのペア, 7|プレイヤー1|
|5|2H 2D 4C 4D 4S<br>4-2のフルハウス|3C 3D 3S 9S 9D<br>3-9のフルハウス|プレイヤー1|

[poker.txt](https://projecteuler.net/project/resources/p054_poker.txt) には1000個のランダムな手札の組が含まれている. 各行は10枚のカードからなる (スペースで区切られている): 最初の5枚がプレイヤー1の手札であり, 残りの5枚がプレイヤー2の手札である. 以下のことを仮定してよい

全ての手札は正しい (使われない文字が出現しない. 同じカードは繰り返されない)
各プレイヤーの手札は特に決まった順に並んでいるわけではない
各勝負で勝敗は必ず決まる
1000回中プレイヤー1が勝つのは何回か? (訳注 : この問題に置いてA 2 3 4 5というストレートは考えなくてもよい)

```python
from collections import Counter

def hoge(path):
    def duel(cards):
        P1, P2 = calc(cards[:5]), calc(cards[5:])
        p1, p2 = next(P1), next(P2)
        while p1 == p2:
            p1, p2 = next(P1), next(P2)
        return p1 > p2
#
    def calc(hand):
        n = Counter([ card_score[x[0]] for x in hand ])
        nk, nv = n.keys(), n.values()
        s = set([ x[1] for x in hand ])
        f = lambda d, x: max([ k for k, v in d.items() if v == x ])
        # Royal Flush
        if sorted(nk) == list(range(10, 15)) and len(s) == 1:
            yield 10**10
        # Straight flush
        if sorted(nk) == list(range(min(nk), min(nk)+5)) and len(s) == 1:
            yield 10**9 + max(nk)
        # Flush
        if len(s) == 1:
            yield 10**6 + max(nk)
        # Straight
        if sorted(nk) == list(range(min(nk), min(nk)+5)):
            yield 10**5 + max(nk)
        while n:
            # Four of a kind
            if 4 in nv:
                yield 10**8 + pop_and_return_key(n, f(n, 4))
            # Full house
            if sorted(nv) == [2, 3]:
                yield 10**7 + pop_and_return_key(n, f(n, 3))
            # Three of a kind
            if 3 in nv:
                yield 10**4 + pop_and_return_key(n, f(n, 3))
            # Two pair  
            if sorted(nv) == [1, 2, 2]:
                yield 10**3 + pop_and_return_key(n, f(n, 2))
            # One pair
            if 2 in nv:
                yield 10**2 + pop_and_return_key(n, f(n, 2))
            # High card
            yield pop_and_return_key(n, f(n, 1))
#
    def pop_and_return_key(d, k):
        d.pop(k)
        return k
#
    card_score = { x:i for i, x in enumerate(list('23456789TJQKA'), 2) }
    data = open(path).read().strip().split('\n')
    return sum( duel(d.split()) for d in data )

print(hoge('poker.txt'))
```